猫效应 IO monad 是如何工作的?

How does the cats-effect IO monad really work?

我是函数式编程和 Scala 的新手,我正在检查 Cats Effect 框架并试图了解 IO monad 的作用。到目前为止,我所了解的是,在 IO 块中编写代码只是对需要完成的事情的描述,并且在您明确 运行 使用提供的 unsafe 方法之前什么都不会发生,也是一种方式通过实际上不 运行 使执行副作用的代码参照透明。

我尝试执行下面的代码片段只是为了理解它的含义:

object Playground extends App {
  var out = 10
  var state = "paused"

  def changeState(newState: String): IO[Unit] = {
    state = newState
    IO(println("Updated state."))
  }

  def x(string: String): IO[Unit] = {
    out += 1
    IO(println(string))
  }

  val tuple1 = (x("one"), x("two"))

  for {
    _ <- x("1")
    _ <- changeState("playing")
  } yield ()

  println(out)
  println(state)
}

输出为:

13
paused

不明白为什么赋值state = newState不是运行,而是递增和赋值表达式out += 1运行。我是否遗漏了一些关于它应该如何工作的明显信息?我真的需要一些帮助。我知道我可以使用 unsafe 方法将其设置为 运行。

在您的特定示例中,我认为正在发生的事情是常规命令式 Scala 编码不受 IO monad 的影响——它 运行s 在 Scala 规则下通常会受到影响.

当你运行:

for {
  _ <- x("1")
  _ <- changeState("playing")
} yield ()

这会立即调用 x。这与 IO monad 无关; for 理解就是这样定义的。第一步是评估第一个语句,以便您可以对其调用 flatMap

如您所见,您永远不会“运行”一元结果,因此永远不会调用 flatMap 的参数,即一元延续,从而不会调用 changeState .这是 IO monad 特有的,例如,List monad 的 flatMap 会立即调用该函数(除非它是一个空列表)。