Cats Effect 的 IO.suspend 函数到底有什么作用?

LLC*_*pos 1 functional-programming scala scala-cats cats-effect

猫效应有什么作用IO.suspend以及为什么有用?有文档,但并不完全清楚。

文档给出了以下用例:

import cats.effect.IO

def fib(n: Int, a: Long, b: Long): IO[Long] =
  IO.suspend {
    if (n > 0)
      fib(n - 1, b, a + b)
    else
      IO.pure(a)
  }
Run Code Online (Sandbox Code Playgroud)

举个例子,为什么我要使用上面的函数,而不是下面的类似函数?

import cats.effect.IO

import scala.annotation.tailrec

@tailrec
def fib(n: Int, a: Long, b: Long): IO[Long] =
  if (n > 0)
    fib(n -1, b, a + b)
  else
    IO.pure(a)

Run Code Online (Sandbox Code Playgroud)

Mat*_*zok 6

其中一个是懒惰的,另一个是渴望的。

def printFibIfNeeded(n: Int, shouldPrint: Boolean) = {
  val fibResult = fib(n, 0, 1)
  if (shouldPrint) fibResult.map(r => println(r))
  else IO.unit
}
Run Code Online (Sandbox Code Playgroud)

如果您使用suspend, thenfibResult将只是一个计算方法,如果 则不会运行shouldPrint. = false

在第二个示例中,您已经计算了该值并只是用 IO 封装了它,因此您让计算机运行 CPU,尽管最终没有必要。

对于纯粹的计算,它可能看起来像是一种优化,但如果有副作用怎么办?

def dropAllUsersInDB: Future[Unit]

def eagerDrop = IO.fromFuture(IO.pure(dropAllUsersInDB))

def lazyDrop = IO.fromFuture(IO.suspend(IO.pure(dropAllUsersInDB)))
Run Code Online (Sandbox Code Playgroud)

第一个示例创建的内容Future将删除所有用户 - 因此无论我们是否将此 IO 组合到我们的程序中,用户都会被删除。

第二个示例suspend在 IO 配方中的某个位置,因此Future除非评估 IO,否则不会创建它。因此,除非我们明确地将这个 IO 组合成将成为计算一部分的东西,否则用户是安全的。

在您的示例中,如果您这样做,这将变得更加明显:

def fib(n: Int, a: Long, b: Long): IO[Long] =
  IO.suspend {
    println("evaluating")
    if (n > 0)
      fib(n - 1, b, a + b)
    else
      IO.pure(a)
  }
Run Code Online (Sandbox Code Playgroud)

@tailrec
def fib(n: Int, a: Long, b: Long): IO[Long] = {
  println("evaluating")
  if (n > 0)
    fib(n -1, b, a + b)
  else
    IO.pure(a)
}
Run Code Online (Sandbox Code Playgroud)

然后调用fib创建一个 IO 值而不对其进行评估,您就会看到差异。