【问题标题】:Stop for-comprehension mid-flow when using stacked monads of State and IO使用 State 和 IO 的堆叠单子时停止理解中间流
【发布时间】:2019-10-30 12:00:27
【问题描述】:

在这个 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()
  }

【问题讨论】:

    标签: scala functional-programming monads scala-cats


    【解决方案1】:

    如果你想保持状态,那么你可以将另一个 monad 转换器放入组合中,即OptionT,用于短路。然后将包含所有可能返回StopNow 的步骤的整个块提升到OptionT,最后使用getOrElse 带回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返回一个StopNow


    您可能想知道为什么不使用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 结果的情况下,状态没有正确传递,而是所有内容都被删除了,并且() 在“世界尽头”返回。这有点类似于对控制流使用异常,还有一个缺点是它所能做的就是完全退出整个程序。所以,我可能会尝试使用OptionT

    【讨论】:

    • 谢谢,如果我需要打印最终的 BusinessResult 会是什么样子?
    • @jakstack 你的意思是,例如,如果StopNow 实际上是一个StopNow(reason),你想看到reason?好吧,然后取一个EitherT 而不是OptionT,让它存储它之前退出的确切原因,然后不要用() 覆盖它来丢弃它,而是打印它。
    猜你喜欢
    • 2019-10-29
    • 1970-01-01
    • 2019-10-30
    • 2014-01-12
    • 2019-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-26
    相关资源
    最近更新 更多