【问题标题】:How to stack the State and IO monads如何堆叠 State 和 IO monad
【发布时间】:2019-10-29 21:33:58
【问题描述】:
在 Connect4 游戏中:
- 我们从一个空网格开始
- 两名玩家将棋子 x 和 o 放在网格上
- 第一个达到 4 个连线的玩家获胜!
- 这是一款基于文本的主机游戏
我的想法是:
- 在游戏的每一步,网格都会从一种状态转换到另一种状态
- 因此我需要使用 State monad
- 因为这是一个基于控制台的应用程序,涉及到 io
- 在这种情况下,我还需要使用 IO monad
这种想法正确吗?
假设以上是正确的,其中哪一项是正确的?
- 类型 StateInIO[S,A] = IO[State[S,A]]
- 类型 IOInState[S,A] = 状态[S,IO[A]]
我赞成第二种选择,对我来说更有意义。
我可以用这种方式堆叠这些 monad (State, IO) 吗?
【问题讨论】:
标签:
scala
functional-programming
monads
scala-cats
【解决方案1】:
我认为您应该只编写一个工作程序,然后对其进行概括/重构,而不是预先进行过度设计。
您需要State[S, A] 用于业务逻辑,IO[A] 用于与控制台交互。但你不必混合它。
然后在您编写用户交互循环的主类中,您可以利用 StateT 来统一您的两种类型。
顺便说一句,IO[State[S,A]] 看起来不是一个非常有用的类型。它说您可以从现实世界中读取State[S, A]。 State 是一个函数。没有明智的方法可以从现实世界中读取函数。所以很可能你需要一些不那么强大的东西。当然,这种类型在 monad 转换器的上下文中是有意义的。
【解决方案2】:
cats 中的State 实际上定义为monad 转换器StateT[F[_], S, A] 的类型别名,其中F[_] 是效果类型。这个别名用Eval 修复了F[_],所以它看起来像type State[S, A] = StateT[Eval, S, A]。在您的情况下,您应该只定义自己的 IO 状态,例如 type IOState[S, A] = StateT[IO, S, A]。
StateT[IO, S, A] 对应于 IO[S => IO[(S, A)]],我觉得它是更有用的堆栈,因为您现在可以轻松地将使用此堆栈的代码与使用 IO 的应用程序的其余部分连接起来。
您可以在来自猫的状态文档中的interleaving effects 部分中找到更多关于使用StateT 堆叠状态单子和效果单子的信息。
【讨论】:
-
F[S => F[(S, A)]] for F = IO 是IO[S => IO[(S, A)]],而IO[State[S, A]] 本质上是IO[S => (S, A)],因此中间段落似乎不太对。
-
-
另一方面:the documentation 声明 “此数据类型表示 S => F[(S, A)] 形式的计算。”,无论出于何种原因,外部 F 是, 省略了...我不知道为什么。