我试图找到启发式/规则,让我预测正确的行为。
我做了一些实验,并困惑为什么有时我得到渴望,而其他时候懒惰的评估时,使用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,我应该使用纯,如果我想调用一些可能失败的函数,我应该使用延迟?
2条答案
按热度按时间dnph8jn41#
如果IO.pure(1)急切地执行,为什么链接两个pure会给予延迟输出?
pure
和delay
之间的区别在于参数的计算,而不是IO
本身。在使用unsafe*
变体或在IOApp
中运行它之前,始终会得到IO
作为输出。看看签名的区别:
delay
接受一个 by-name 参数,它只在使用时才求值,而pure
会立即求值它的参数。对于文字值1
,实际上没有什么区别,但对于任何可以执行效果的东西,都有很大的区别,如您的示例所示。如果两个pure应该被延迟计算,为什么在第一个pure中抛出异常会立即返回异常?(仅在第二个pure中抛出将返回延迟评估的IO)
第一个
pure
急切地计算它的参数,这抛出了异常。但是对于orElse
,看看它的签名:它接受另一个by-name参数,这意味着它接受 * 值为
IO[B]
* 的值。这会阻止第二个人立即投掷。基于前两个例子,在我看来,orElse作为一个后备工具,第一个IO应该总是一个延迟?
如果它不是纯值,是的。也就是说,不应该将
IO.pure
与非常量一起使用。这取决于输入的含义吗?
是的,
pure
只用于恒定的,无副作用的东西。flvlnr442#
如果IO.pure(1)急切地执行,为什么链接两个pure会给予延迟输出?
因为设计有一个父trait(IO),并且许多这些情况实际上使用子类来表示不同的操作:
调用方法会返回到定义方法的父类,并且不知道它的Pure还是延迟,所以它必须把一切都当作延迟。
如果两个pure应该被延迟计算,为什么在第一个pure中抛出异常会立即返回异常?(仅在第二个pure中抛出将返回延迟评估的IO)
Pure应该仅用于安全值。您的陈述:
例如,如果我想硬编码一个2,我应该使用纯,如果我想调用一些可能失败的函数,我应该使用延迟?
是正确的。Pure是指不能失败的操作。延迟是指可能失败的操作。你可以把Delay看作是一个临时状态,在计算时将其放入Pure或Error中。如果你按照这个结论去做,那么:
基于前两个例子,在我看来,orElse作为一个后备工具,第一个IO应该总是一个延迟?
也是正确的,如果你不能失败,那么
orElse
就没有多大意义。我建议浏览source code,它可以很容易访问。查看Getting Started Guide