在使用 State 和 IO 的堆叠 monad 时停止理解中流
Stop for-comprehension mid-flow when using stacked monads of State and IO
在这个 Scala 示例中,我需要在结果为 StopNow
时停止,我需要在调用 decisionStep
之后执行此操作。
我该怎么做?
case class BusinessState()
trait BusinessResult
case object KeepGoing extends BusinessResult
case object StopNow extends BusinessResult
type IOState[S, A] = StateT[IO, S, A]
type BusinessIOState[A] = IOState[BusinessState, A]
trait SomeSteps {
def step1:BusinessIOState[Unit]
def step2:BusinessIOState[BusinessState]
def decisionStep:BusinessIOState[BusinessResult]
def step3:BusinessIOState[BusinessResult]
def step4:BusinessIOState[BusinessResult]
def program = for {
_ <- step1
businessState <- step2
businessResult <- decisionStep
businessResult1 <- step3
businessResult2 <- step4
} yield()
}
如果你想保持状态,那么你可以将另一个 monad 转换器加入其中,即 OptionT
,用于 short-circuiting。整个区块包含可能 return 和 StopNow
的所有步骤然后被提升到 OptionT
,最后使用 getOrElse
:[=28 带回 BusinessIOState
=]
import cats._
import cats.data._
import cats.syntax._
import cats.effect._
object StopIO extends IOApp {
case class BusinessState()
trait BusinessResult
case object KeepGoing extends BusinessResult
case object StopNow extends BusinessResult
type IOState[S, A] = StateT[IO, S, A]
type BusinessIOState[A] = IOState[BusinessState, A]
trait SomeSteps {
def step1: BusinessIOState[Unit]
def step2: BusinessIOState[BusinessState]
def decisionStep: BusinessIOState[BusinessResult]
def step3: BusinessIOState[BusinessResult]
def step4: BusinessIOState[BusinessResult]
def toOpt(a: BusinessIOState[BusinessResult])
: OptionT[BusinessIOState, BusinessResult] = {
OptionT.liftF(a).filter(_ == KeepGoing)
}
def program: BusinessIOState[Unit] = (for {
_ <- step1
businessState <- step2
_ <- (for {
_ <- toOpt(decisionStep)
_ <- toOpt(step3)
_ <- toOpt(step4)
} yield ()).getOrElse(())
} yield ())
}
object Impl extends SomeSteps {
def step1 = Monad[BusinessIOState].unit
def step2 = Monad[BusinessIOState].pure(BusinessState())
def decisionStep = StateT.liftF(IO { println("dS"); KeepGoing })
def step3 = StateT.liftF(IO { println("3"); StopNow })
def step4 = StateT.liftF(IO { println("4"); KeepGoing })
}
def run(args: List[String]) = for {
_ <- Impl.program.runA(BusinessState())
} yield ExitCode.Success
}
输出为:
dS
3
注意没有出现4
,程序停止得比较早,因为step3
returns a StopNow
.
您可能想知道为什么不对 short-circuiting 使用 MonadError[IO, Throwable]
的功能,因为 IO
已经可以处理因抛出的异常而停止的计算。它可能是这样的:
import cats._
import cats.data._
import cats.syntax._
import cats.effect._
object StopIO extends IOApp {
case class BusinessState()
trait BusinessResult
case object KeepGoing extends BusinessResult
case object StopNow extends BusinessResult
type IOState[S, A] = StateT[IO, S, A]
type BusinessIOState[A] = IOState[BusinessState, A]
trait SomeSteps {
def step1: BusinessIOState[Unit]
def step2: BusinessIOState[BusinessState]
def decisionStep: BusinessIOState[BusinessResult]
def step3: BusinessIOState[BusinessResult]
def step4: BusinessIOState[BusinessResult]
def raiseStop(a: BusinessIOState[BusinessResult])
: BusinessIOState[Unit] = {
a.flatMap {
case KeepGoing => StateT.liftF(IO.unit)
case StopNow => StateT.liftF(
MonadError[IO, Throwable].raiseError(new Exception("stop now"))
)
}
}
def program = (for {
_ <- step1
businessState <- step2
_ <- raiseStop(decisionStep)
_ <- raiseStop(step3)
_ <- raiseStop(step4)
} yield ())
}
object Impl extends SomeSteps {
def step1 = Monad[BusinessIOState].unit
def step2 = Monad[BusinessIOState].pure(BusinessState())
def decisionStep = StateT.liftF(IO { println("dS"); KeepGoing })
def step3 = StateT.liftF(IO { println("3"); StopNow })
def step4 = StateT.liftF(IO { println("4"); KeepGoing })
}
def run(args: List[String]) = for {
_ <- Impl.program.runA(BusinessState()).handleErrorWith(_ => IO.unit)
} yield ExitCode.Success
}
同样,输出是:
dS
3
我认为与 OptionT
版本相比,它既不短也不清晰,而且它还有一个缺点,即在 StopNow
结果的情况下,状态没有正确传递,但是相反,所有内容都被删除,()
在 "end of the world" 处被 return 编辑。这有点类似于对控制流使用异常,但还有一个缺点,即它所能做的就是完全退出整个程序。所以,我可能会尝试使用 OptionT
.
在这个 Scala 示例中,我需要在结果为 StopNow
时停止,我需要在调用 decisionStep
之后执行此操作。
我该怎么做?
case class BusinessState()
trait BusinessResult
case object KeepGoing extends BusinessResult
case object StopNow extends BusinessResult
type IOState[S, A] = StateT[IO, S, A]
type BusinessIOState[A] = IOState[BusinessState, A]
trait SomeSteps {
def step1:BusinessIOState[Unit]
def step2:BusinessIOState[BusinessState]
def decisionStep:BusinessIOState[BusinessResult]
def step3:BusinessIOState[BusinessResult]
def step4:BusinessIOState[BusinessResult]
def program = for {
_ <- step1
businessState <- step2
businessResult <- decisionStep
businessResult1 <- step3
businessResult2 <- step4
} yield()
}
如果你想保持状态,那么你可以将另一个 monad 转换器加入其中,即 OptionT
,用于 short-circuiting。整个区块包含可能 return 和 StopNow
的所有步骤然后被提升到 OptionT
,最后使用 getOrElse
:[=28 带回 BusinessIOState
=]
import cats._
import cats.data._
import cats.syntax._
import cats.effect._
object StopIO extends IOApp {
case class BusinessState()
trait BusinessResult
case object KeepGoing extends BusinessResult
case object StopNow extends BusinessResult
type IOState[S, A] = StateT[IO, S, A]
type BusinessIOState[A] = IOState[BusinessState, A]
trait SomeSteps {
def step1: BusinessIOState[Unit]
def step2: BusinessIOState[BusinessState]
def decisionStep: BusinessIOState[BusinessResult]
def step3: BusinessIOState[BusinessResult]
def step4: BusinessIOState[BusinessResult]
def toOpt(a: BusinessIOState[BusinessResult])
: OptionT[BusinessIOState, BusinessResult] = {
OptionT.liftF(a).filter(_ == KeepGoing)
}
def program: BusinessIOState[Unit] = (for {
_ <- step1
businessState <- step2
_ <- (for {
_ <- toOpt(decisionStep)
_ <- toOpt(step3)
_ <- toOpt(step4)
} yield ()).getOrElse(())
} yield ())
}
object Impl extends SomeSteps {
def step1 = Monad[BusinessIOState].unit
def step2 = Monad[BusinessIOState].pure(BusinessState())
def decisionStep = StateT.liftF(IO { println("dS"); KeepGoing })
def step3 = StateT.liftF(IO { println("3"); StopNow })
def step4 = StateT.liftF(IO { println("4"); KeepGoing })
}
def run(args: List[String]) = for {
_ <- Impl.program.runA(BusinessState())
} yield ExitCode.Success
}
输出为:
dS
3
注意没有出现4
,程序停止得比较早,因为step3
returns a StopNow
.
您可能想知道为什么不对 short-circuiting 使用 MonadError[IO, Throwable]
的功能,因为 IO
已经可以处理因抛出的异常而停止的计算。它可能是这样的:
import cats._
import cats.data._
import cats.syntax._
import cats.effect._
object StopIO extends IOApp {
case class BusinessState()
trait BusinessResult
case object KeepGoing extends BusinessResult
case object StopNow extends BusinessResult
type IOState[S, A] = StateT[IO, S, A]
type BusinessIOState[A] = IOState[BusinessState, A]
trait SomeSteps {
def step1: BusinessIOState[Unit]
def step2: BusinessIOState[BusinessState]
def decisionStep: BusinessIOState[BusinessResult]
def step3: BusinessIOState[BusinessResult]
def step4: BusinessIOState[BusinessResult]
def raiseStop(a: BusinessIOState[BusinessResult])
: BusinessIOState[Unit] = {
a.flatMap {
case KeepGoing => StateT.liftF(IO.unit)
case StopNow => StateT.liftF(
MonadError[IO, Throwable].raiseError(new Exception("stop now"))
)
}
}
def program = (for {
_ <- step1
businessState <- step2
_ <- raiseStop(decisionStep)
_ <- raiseStop(step3)
_ <- raiseStop(step4)
} yield ())
}
object Impl extends SomeSteps {
def step1 = Monad[BusinessIOState].unit
def step2 = Monad[BusinessIOState].pure(BusinessState())
def decisionStep = StateT.liftF(IO { println("dS"); KeepGoing })
def step3 = StateT.liftF(IO { println("3"); StopNow })
def step4 = StateT.liftF(IO { println("4"); KeepGoing })
}
def run(args: List[String]) = for {
_ <- Impl.program.runA(BusinessState()).handleErrorWith(_ => IO.unit)
} yield ExitCode.Success
}
同样,输出是:
dS
3
我认为与 OptionT
版本相比,它既不短也不清晰,而且它还有一个缺点,即在 StopNow
结果的情况下,状态没有正确传递,但是相反,所有内容都被删除,()
在 "end of the world" 处被 return 编辑。这有点类似于对控制流使用异常,但还有一个缺点,即它所能做的就是完全退出整个程序。所以,我可能会尝试使用 OptionT
.