当使用State和IO的堆叠式monad时,停止全面理解

时间:2019-06-16 19:35:59

标签: scala functional-programming monads scala-cats

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

1 个答案:

答案 0 :(得分:4)

如果要保持状态,则可以将另一个monad变压器(即OptionT)投入混合,以进行短路。然后,将包含所有可能返回StopNow的所有步骤的整个块放到OptionT中,最后使用BusinessIOState带回到getOrElse

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