scala 效果系统(例如ZIO)的好处是什么?

epggiuax  于 12个月前  发布在  Scala
关注(0)|答案(4)|浏览(272)

我很难理解什么是价值效应系统,比如ZIO或猫效应。

  • 它不会使代码可读,例如:
val wrappedB = for {
   a <- getA()  // : ZIO[R, E, A]
   b <- getB(a) // : ZIO[R, E, B]
} yield b

字符串
对我来说并不比

val a = getA()  // : A
val b = getB(a) // : B


我甚至可以说,后者更直接,因为调用函数会执行它,而不仅仅是创建效果或执行管道。

*延迟执行听起来并不令人信服,因为到目前为止我遇到的所有例子都只是立即执行流水线。能够并行或多次执行效果可以通过更简单的方法来实现IMHO,例如C#具有Parallel.ForEach
*可组合性。函数可以在不使用效果的情况下组合,例如通过简单组合。
*纯函数方法。最后纯 * 指令 * 会被执行,所以看起来就像是假装DB访问是纯的。这对推理没有帮助,因为虽然指令的构造是纯的,但执行它们不是。

我可能忽略了一些东西,或者只是淡化了上面的好处,或者在某些情况下(例如复杂的领域),好处可能更大。使用效果系统的最大卖点是什么?

6tqwzwtp

6tqwzwtp1#

因为它可以很容易地处理副作用。从你的例子:

a <- getA()  // ZIO[R, E, A] (doesn't have to be ZIO btw)

val a = getA(): A

字符串
第一个getA说明了效果和返回错误的可能性,这是一个副作用。这就像从某个数据库中获取一个A,其中所述A可能不存在或您没有访问它的权限。第二个getA就像一个简单的def getA = "A"
我们如何将这些方法放在一起?如果其中一个方法抛出错误怎么办?我们应该继续执行下一个方法还是直接退出它?如果其中一个方法阻塞了你的线程怎么办?
希望这解决了你关于可组合性的第二个问题。快速解决剩下的问题:

  • 延迟执行。这可能有两个原因。第一个是你实际上不想意外地启动一个执行。或者只是因为你写了它,它就马上开始了。这打破了酷家伙们所说的引用透明性。第二个是并发执行需要线程池或执行上下文。通常我们希望有一个集中的地方,我们可以在整个应用程序中进行微调。当构建一个库的时候,我们不能自己提供它。它是由用户提供的。事实上,我们也可以延迟效果。你所做的就是定义效果应该如何表现,用户可以使用ZIO,Monix等,这完全取决于他们。
  • 纯粹性。从技术上讲,将一个进程 Package 在一个纯粹的效果中并不一定意味着底层进程实际上使用了它。只有实现才知道它是否真的被使用了。我们可以做的是提升它,使其与组合兼容。
dgsult0t

dgsult0t2#

是什么让ZIO或猫编程伟大的是当谈到并发编程。他们也是其他原因,但这一个是IMHO在那里我得到了“啊啊!现在我得到了”。
试着写一个程序来监视多个文件夹的内容,并为每个添加到文件夹中的文件解析它们的内容,但同时不超过4个文件。(就像Adam Fraser在youtube https://www.youtube.com/watch?v=wxpkMojvz24上的视频“What Java developers could learn from ZIO”中的例子。
我的意思是,在ZIO中,这真的很容易写:)
为了使数据结构更大,你将合并数据结构(ZIO是一种数据结构)结合起来,这一事实背后的所有想法都很容易理解,我不想在没有它的情况下编写复杂问题的代码:)

axr492tv

axr492tv3#

这两个例子是不可比较的,因为第一个语句中的错误会将第一种形式中与对象化序列相等的值标记为错误,而第二种形式则会使整个程序停止。第二种形式应该是一个函数定义,以正确地封装这两个语句,然后是对其调用结果的影响。
但更重要的是,为了完全模仿第一种形式,必须编写一些额外的代码,以捕获异常并构建真正的错误结果,而所有这些都是由ZIO免费制作的。
我认为在连续语句之间干净地传播错误状态的能力是ZIO方法的真实的价值。任何复合ZIO程序片段都是完全可组合的。无论如何,这是任何基于工作流的方法的主要好处。
正是这种模块性赋予了效果处理它的真实的价值。由于效果是一个在结构上可能产生错误的动作,像这样处理效果是一个以可组合的方式处理错误的好方法。事实上,处理效果包括处理错误!

4ioopgfo

4ioopgfo4#

是的,在一个简单的用例中,这是一个过度的使用。现在想象一下,如果函数getA()可以抛出异常,函数getB(a)调用第三方服务,内部有昂贵的I/O操作,并返回nullable。因此,代码看起来像这样:

def getAOOP: A
def getBOOP: B

def getAEffect: Either[Exception, A] 
def getBEffect: IO[Option[B]]

字符串
现在让我们重新考虑一下上面提到的好处:

*延迟执行-没有效果你的代码突然变成了一个雷区,因为你的函数可以随机抛出和阻塞执行线程(如果你在执行池上运行东西而没有显式处理这种情况,这可能是灾难性的)。有了效果,你可以更安全,更容易地配置执行池,而不会在运行时出现池饥饿/耗尽的惊喜。
***可组合性。**出于类似的原因,现在组合起来要容易得多。因为你确切地知道这些函数在做什么,而且你有像Traversable/Sequencable这样的工具来促进这里的组合。你可能会认为有3个嵌套类型很难,但是为了简单起见,它们可以在更高的层次上很好地折叠成另一个(因此,除了IO[Either[Throwable, B]]之外,您还可以使用IO[B],因为IO已经有错误通道。
***纯函数方法。**将它们作为纯描述会带来很多新的可能性,我已经提到过,将IO作为I/O操作的描述可以在配置线程池和执行模式时实现灵活性和方便性。例如,对于DB操作也是如此。将它们作为纯描述可以将连接逻辑提取到单独的地方,例如,你可以让DBIO描述 * 什么 * 需要运行,Transactor描述 * 如何 * 需要运行,等等,这反过来又使你能够编写更多的维护代码。

此外:它更容易改变,更容易独立地分析代码,最后但并非最不重要的是,它与Cats.effect或Zio等Effect ecosystem集成得很好。

相关问题