像 Eithers 这样的错误处理单子如何实现引用透明性?

How do error handling monads like Eithers achieve referential transparency?

通过阅读 FP,我对消除副作用的好处的理解是,如果我们所有的函数都是 pure/have 引用透明(只有在没有副作用的情况下才能实现的东西),那么我们的函数更易于测试、调试、重用,并且更加模块化。

由于异常是一种副作用,我们需要避免抛出异常。显然我们仍然需要能够在出现问题时终止进程,因此 FP 使用 monad 来实现引用透明性和处理异常的能力。

我感到困惑的是 monad 究竟是如何实现这一点的。假设我有这段代码使用 scalaz

def couldThrowException: Exception \/ Boolean = ??
val f  = couldThrowException()
val g = couldThrowException()

由于 couldThrowException 可能 return 例外,因此无法保证 fg 相同。 f 可以是 \/-(true)g 可以是 -\/(NullPointerException)。由于 couldThrowException 可以 return 具有相同输入的不同值,因此它不是纯函数。使用 monad 不是为了让我们的函数保持纯净吗?

f()g() 应该 计算出相同的值,给定相同的输入。

在纯 FP 中,没有参数的函数在每次调用时都必须计算出相同的结果。因此,如果您的 couldThrowException 有时 return 有时 \/-(true) 有时 -\/(NullPointerException).

这不是纯粹的 FP

如果 couldThrowException 接受参数,则 return Either 更有意义。如果它是一个纯函数,它将具有引用透明性,因此某些输入将始终导致 \/-(true) 而某些输入将始终导致 -\/(NullPointerException).

在 Scala 中,您可能会使用 not 纯函数,并且 not 引用透明。也许是 Java class。也许它使用了不纯的 Scala 的一部分。

但我猜您对在纯 FP 世界和非纯库之间架起桥梁感兴趣。一个 classic 的例子是 IO。 println 可能由于各种原因而失败 - 权限、文件系统已满等。

在 FP 中处理此问题的 classic 方法是使用一个 IO 函数,该函数将 "world" 状态作为输入参数,并且 returns 都是IO调用,以及新的"world"状态。状态可以是一个 "fake" 值,由不纯语言的库代码支持,但这意味着每次调用该函数时,您都会传递一个不同的状态,因此它是引用透明的。

通常,monad 用于封装 "world"。

您可以通过阅读 Haskell 的 IO monad 了解很多关于这种方法的信息。

Core Scala IO 并非完全纯净,因此 println 可能会抛出异常,因此,如您所见,它并不是完全引用透明的。 Scalaz 提供了一个类似于 Haskell 的 IO monad。

需要注意的一件事,因为它会绊倒很多初学者:"World" 方法没有任何需要 monad 的地方,而且 IO 在第一次学习 monad 时并不容易看monad 是什么以及为什么它们有用。