scala cats-effect IO. or Else是否只对延迟IO有效而不对纯IO有效?

icomxhvb  于 2023-06-06  发布在  Scala
关注(0)|答案(2)|浏览(357)

我试图找到启发式/规则,让我预测正确的行为。
我做了一些实验,并困惑为什么有时我得到渴望,而其他时候懒惰的评估时,使用orElse链的各种组合的纯/延迟和价值观在他们。
https://typelevel.org/cats-effect/api/3.x/cats/effect/IO.html的IO.orElse文档不清楚。
我在sbt console中运行示例基于build.sbt我使用scalaVersion := "3.2.2""cats-effect" % "3.4.10"
所有延迟答案都通过链接unsafeRunSync()进行评估,如

scala> IO.pure(1).orElse(
     | IO.pure(2)).unsafeRunSync()
val res53: Int = 1

问题
实施例1

scala> IO.pure(1).orElse(
     | IO.pure(2))
val res45: cats.effect.IO[Int] = IO(...)

如果IO.pure(1)急切地执行,为什么链接两个pure会给予延迟输出?

实施例2

scala> IO.pure(throw new Exception).orElse(
     | IO.pure(2))
java.lang.Exception
  ... 62 elided

如果两个pure应该被延迟计算,为什么在第一个pure中抛出异常会立即返回异常?(仅在第二个pure中抛出将返回延迟评估的IO)
基于前两个例子,在我看来,orElse作为一个后备工具,第一个IO应该总是一个延迟?

实施例3

scala> IO.delay(throw new Exception).orElse(
     | IO.pure(2)).unsafeRunSync()
val res62: Int = 2

实施例4

scala> IO.delay(throw new Exception).orElse(
     | IO.delay(2)).unsafeRunSync()
val res63: Int = 2

示例3和示例4在unsafeRunSync之前和之后返回相同的结果。那么我如何选择在第二个IO(或其他输入)中使用pure或delay?
这取决于输入的含义吗?例如,如果我想硬编码一个2,我应该使用纯,如果我想调用一些可能失败的函数,我应该使用延迟?

dnph8jn4

dnph8jn41#

如果IO.pure(1)急切地执行,为什么链接两个pure会给予延迟输出?
puredelay之间的区别在于参数的计算,而不是IO本身。在使用unsafe*变体或在IOApp中运行它之前,始终会得到IO作为输出。
看看签名的区别:

def delay[A](thunk: => A): IO[A]

def pure[A](value: A): IO[A]

delay接受一个 by-name 参数,它只在使用时才求值,而pure会立即求值它的参数。对于文字值1,实际上没有什么区别,但对于任何可以执行效果的东西,都有很大的区别,如您的示例所示。
如果两个pure应该被延迟计算,为什么在第一个pure中抛出异常会立即返回异常?(仅在第二个pure中抛出将返回延迟评估的IO)
第一个pure急切地计算它的参数,这抛出了异常。但是对于orElse,看看它的签名:

def orElse[B >: A](other: => IO[B]): IO[B]

它接受另一个by-name参数,这意味着它接受 * 值为IO[B] * 的值。这会阻止第二个人立即投掷。
基于前两个例子,在我看来,orElse作为一个后备工具,第一个IO应该总是一个延迟?
如果它不是纯值,是的。也就是说,不应该将IO.pure与非常量一起使用。
这取决于输入的含义吗?
是的,pure只用于恒定的,无副作用的东西。

flvlnr44

flvlnr442#

如果IO.pure(1)急切地执行,为什么链接两个pure会给予延迟输出?
因为设计有一个父trait(IO),并且许多这些情况实际上使用子类来表示不同的操作:

private[effect] final case class Pure[+A](value: A) extends IO[A] {
  def tag = 0
  override def toString: String = s"IO($value)"
}

private[effect] final case class Error(t: Throwable) extends IO[Nothing] {
  def tag = 1
}

private[effect] final case class Delay[+A](thunk: () => A, event: TracingEvent)
  extends IO[A] {
  def tag = 2
}
...

调用方法会返回到定义方法的父类,并且不知道它的Pure还是延迟,所以它必须把一切都当作延迟。
如果两个pure应该被延迟计算,为什么在第一个pure中抛出异常会立即返回异常?(仅在第二个pure中抛出将返回延迟评估的IO)
Pure应该仅用于安全值。您的陈述:
例如,如果我想硬编码一个2,我应该使用纯,如果我想调用一些可能失败的函数,我应该使用延迟?
是正确的。Pure是指不能失败的操作。延迟是指可能失败的操作。你可以把Delay看作是一个临时状态,在计算时将其放入Pure或Error中。如果你按照这个结论去做,那么:
基于前两个例子,在我看来,orElse作为一个后备工具,第一个IO应该总是一个延迟?
也是正确的,如果你不能失败,那么orElse就没有多大意义。
我建议浏览source code,它可以很容易访问。查看Getting Started Guide

相关问题