通过阅读FP,我对去除副作用的好处的理解是,如果我们所有的函数都是纯的/具有引用透明性(只能在没有副作用的情况下实现),那么我们的函数更容易测试,调试,重用,更模块化。
由于异常是副作用的一种形式,我们需要避免抛出异常。显然我们仍然需要能够在出现问题时终止进程,因此FP使用monads来实现引用透明性和处理异常的能力。
令我困惑的是monad是如何实现这一目标的。假设我有使用scalaz的代码
def couldThrowException: Exception \/ Boolean = ??
val f = couldThrowException()
val g = couldThrowException()
由于couldThrowException
可能会返回一个例外,因此没有任何关于f
和g
将会相同的人。 f
可能是\/-(true)
和g
是-\/(NullPointerException)
。由于couldThrowException
可以使用相同的输入返回不同的值,因此它不是纯函数。使用monads是不是要保持我们的功能纯粹?
在给定相同输入的情况下,f()
和g()
应评估相同的值。
在纯FP中,没有参数的函数必须在每次调用时都评估为相同的结果。因此,如果你的couldThrowException
有时会返回\/-(true)
,有时会返回-\/(NullPointerException)
,那么它不是纯粹的FP。
如果couldThrowException
接受参数,则返回Either更有意义。如果它是纯函数,它将具有参考透明度,因此一些输入将始终导致\/-(true)
,并且一些输入将始终导致-\/(NullPointerException)
。
在Scala中,您可能正在使用一种不纯粹的功能,而不是引用透明的功能。也许这是一个Java类。也许它使用Scala的一部分并不纯粹。
但我猜你有兴趣在纯FP世界和不纯的图书馆之间架起桥梁。一个典型的例子就是IO。 println
可能由于各种原因而失败 - 权限,文件系统已满等。
在FP中处理此问题的经典方法是使用IO函数,该函数将“world”状态作为输入参数,并返回IO调用的结果和新的“world”状态。状态可以是由不纯的语言中的库代码支持的“假”值,但它意味着每次调用函数时,您传递的是不同的状态,因此它是引用透明的。
通常,monad用于封装“世界”。
通过阅读Haskell的IO monad,你可以找到很多关于这种方法的信息。
Core Scala IO并不是完全纯粹的,所以println
可能会抛出异常,因此,正如您所发现的那样,并不是完全透明的。 Scalaz提供类似于Haskell的IO monad。
需要注意的一点是,因为它让许多初学者蠢蠢欲动:没有什么关于需要monad的“世界”方法,而且当第一次学习monad是什么以及为什么他们是monad时,IO并不是最容易看到的monad有用。