【问题标题】:How to write for comprehension in cats with IO monad如何使用 IO monad 编写猫的理解力
【发布时间】:2019-11-11 16:49:37
【问题描述】:

我有以下代码:

import cats.effect.IO
import cats.data.State
import cats.data.StateT
import cats.implicits._
import cats.effect.LiftIO

abstract class Example {
    object implicits {
        implicit def myEffectLiftIO: LiftIO[IOGameplay] =
            new LiftIO[IOGameplay] {
                override def liftIO[A](ioa: IO[A]): IOGameplay[A] = {
                    StateT.liftF(ioa)
                }
            }
    }

    type Gameplay[A] = State[GameState, A]
    type IOGameplay[A] = StateT[IO, GameState, A]
    type EitherDirection[A] = Either[Throwable, A]

    type Map = Array[Array[FieldType]]
    sealed trait FieldType
    case class GameState(map: Map, block: Block)
    case class Block(f1: Field, f2: Field) 
    case class Field()

    import implicits._
    val L = implicitly[LiftIO[IOGameplay]]

    sealed trait GameResult
    sealed trait Direction

    trait IOMonad {
        def println(msg: String): IO[Unit]
        def readln(): IO[String]
    }

    def play(io: IOMonad): StateT[IO, GameState, GameResult] = {
        val L = implicitly[LiftIO[IOGameplay]]

        for {
            // print map to the console
            _ <- L.liftIO(io.println("Next move: "))
            directionOpt <- L.liftIO(readDirection(io))
            direction <- StateT.liftF[IO, GameState, Direction](IO.fromEither(directionOpt))
            nextBlock <- IO(nextBlock(direction))
            gameResult <- calculate(nextBlock)
        } yield {
            gameResult
        }
    }

    def readDirection(io: IOMonad): IO[EitherDirection[Direction]]
    def nextBlock(direction: Direction): Gameplay[Block]
    def calculate(block: Block): Gameplay[GameResult]
}


这并不完全准确,但我发布了整个块来解释问题。
在这里,我对值进行了许多转换以产生 IO 并将其转换为 StateT。有没有更聪明的方法来做到这一点?也许我应该以某种方式将 io 任务与主算法分开,即与这个理解分开?还是我应该这样做?

【问题讨论】:

  • 如果你发布一个完整的例子会更容易帮助你。我建议不要从您的实际代码中发布 sn-p(留出很大的解释空间,例如 L),而是建议创建一个可以成功编译的小示例程序(例如在 scalafiddle 中)。
  • @MarkusAppel 我发布了整个代码。由于play方法中的理解,它不会编译,这实际上是我的问题的主题。如果您能就任何其他可以改进的事情给我建议,我将非常感激。
  • 看起来好多了,现在人们可以更轻松地找出问题所在。感谢您听取建议。 :)

标签: scala scala-cats cats-effect


【解决方案1】:

一个问题是您的Gameplay 类型与IOGameplay 不兼容,因为Gameplay 使用Eval monad。我假设你想要这个:

    type Gameplay[F[_], A] = StateT[F, GameState, A]
    type IOGameplay[A] = Gameplay[IO, A]

这些方法需要返回 IOGameplay 实例(或者您可以稍后在程序中解除它们):

    def nextBlock(direction: Direction): IOGameplay[Block]
    def calculate(block: Block): IOGameplay[GameResult]

然后for-comprehension编译稍作调整:

      for {
        // print map to the console
        _ <- L.liftIO(io.println("Next move: "))
        directionOpt <- L.liftIO(readDirection(io))
        direction <- StateT.liftF[IO, GameState, Direction](IO.fromEither(directionOpt))
        nextBlock <- nextBlock(direction)
        gameResult <- calculate(nextBlock)
      } yield {
        gameResult
      }

顺便说一句,这个程序中IO 效果的预期目的是什么?用户输入?

【讨论】:

    【解决方案2】:

    如果您的目标是避免将东西从一个 monad 转移到另一个,那么您可以使您的方法和接口具有多态性,以便它们可以与不同的 monad 一起工作,而不仅仅是 IO。以下是如何为您的 IOMonad 特征执行此操作:

      trait IOMonad[F[_]] {
        def println(msg: String): F[Unit]
        def readln(): F[String]
      }
    

    我们的想法是不承诺任何特定的 monad,而是让事情适用于任何提供特定用例所需功能的 monad。在IOMonad 示例中,我们需要运行同步副作用的能力,因此我们通过传递Sync[F] 类型的参数来表达这一点:

    import cats.effect.Sync
    object IOMonad {
      def apply[F[_]](implicit F: Sync[F]) = new IOMonad[F] {
        def println(msg: String): F[Unit] = F.delay(println(msg))
        def readln(): F[String] = F.delay(scala.io.StdIn.readLine())
      }
    }
    
    

    您程序中的其他操作需要不同的功能。例如readDirection 需要进行控制台 IO 并引发Throwable 类型的错误。引发错误的能力由MonadError trait 表示,所以你得到这个签名:

    def readDirection[F[_]](
      io: IOMonad[F])(implicit monErr: MonadError[F, Throwable]
    ): F[Direction]
    

    需要注意的是,我们没有在这里传递Sync[F],因为我们不需要它; IOMonad[F] 对象就足够了。这很重要,因为它允许您以其他不一定涉及副作用的方式实现IOMonad 接口,尤其是用于测试。

    另一个例子是nextBlockcalculate。这些需要操纵GameState 类型的状态,而操纵状态的能力由MonadState 类型表示:

    def nextBlock[F[_]](
      direction: Direction)(implicit F: MonadState[F, GameState]
    ): F[Block]
    
    def calculate[F[_]](
      block: Block)(implicit F: MonadState[F, GameState]
    ): F[GameResult]
    

    很遗憾,MonadState 不包含在猫或猫效应中,您需要 cats-mtl 库。

    当你把所有这些放在一起时,你最终会得到一个这样的程序:

    import cats.MonadError
    import cats.mtl.MonadState
    import cats.implicits._
    
    abstract class Example {
      type Map = Array[Array[FieldType]]
      sealed trait FieldType
      case class GameState(map: Map, block: Block)
      case class Block(f1: Field, f2: Field)
      case class Field()
    
      sealed trait GameResult
      sealed trait Direction
    
      trait IOMonad[F[_]] {
        def println(msg: String): F[Unit]
        def readln(): F[String]
      }
    
      def play[F[_]](
        io: IOMonad[F])(
        implicit merr: MonadError[F, Throwable],
        mst: MonadState[F, GameState]
      ): F[GameResult] = {
        for {
          // print map to the console
          _ <- io.println("Next move: ")
          direction <- readDirection(io)
          nextBlock <- nextBlock[F](direction)
          gameResult <- calculate[F](nextBlock)
        } yield gameResult
      }
    
      def readDirection[F[_]](
        io: IOMonad[F])(
        implicit merr: MonadError[F, Throwable]
      ): F[Direction]
    
      def nextBlock[F[_]](
        direction: Direction)(
        implicit merr: MonadState[F, GameState]
      ): F[Block]
    
      def calculate[F[_]](
        block: Block)(
        implicit mst: MonadState[F, GameState]
      ): F[GameResult]
    }
    

    请注意,每个具体的 Monad 都消失了——在上面的程序中没有 IO,没有 State,没有 Either,连同这些,在不同 monad 之间转换或提升的任何必要性也消失了.

    但请注意,这种编程风格(称为 MTL 风格)有其缺点。

    • 类型推断通常不起作用。在此示例中,您需要将 F 参数显式传递给 nextBlockcalculate,因为 Scala 无法推断它
    • 如前所述,cat 不包含所有必需的类型类,如MonadState,因此您需要额外的库,如cats-mtl
    • 对于新手来说有点难以理解

    这就是为什么部分 Scala 社区(尤其是 John de Goes 和他的 ZIO 工作)不再鼓励 MTL 风格的原因。其他人一直在推动它,因为它允许代码与不同的效果类型重用。

    【讨论】:

      猜你喜欢
      • 2018-05-05
      • 2019-11-12
      • 2018-05-06
      • 2021-03-21
      • 1970-01-01
      • 2016-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多