我通过下面的类比来理解这些东西,我将用 JavaScript 来表达。
如何表达一个有副作用的计算?
1.一个函数
这显然是首先想到的:
var launchRockets = function () {
prepareRockets( queryDBForPreparationParameters() )
launchAllPreparedRockets()
outputResults()
}
你可以看到一个有效的函数调用了一堆其他有效的函数,这些函数本身可以产生未知的效果以及随之而来的所有后果。
2. 使用说明
表达这一点的另一种方式是编写一组指令,描述这些有效的计算,以便稍后执行某些函数。(曾经编写过 SQL 查询吗?)
var launchRocketsInstructions = [
{
description: "Prepare rockets",
parameters: {
description: "Query a DB for preparation parameters"
}
},
{
description: "Launch all prepared rockets"
},
{
description: "Output results"
}
]
那么我们在第二个例子中看到了什么?我们看到一个不可变的数据树来描述计算而不是立即执行它。这里没有副作用,为了组成这个数据树,我们当然可以使用纯函数。这就是 Haskell 中副作用的本质所在。该语言提供的所有基础设施:monads、the IO
、do
-notation - 这些只是工具和抽象,简化了您编写单个指令树的任务。
当然,要真正执行这些指令,最终将不得不逃入副作用的狂野世界。在 JavaScript 的情况下,它类似于execute(launchRocketsInstructions)
,在 Haskell 的情况下,它是运行时执行您使用主模块的功能生成的指令树的根main
,它成为您程序的单一入口点。因此,Haskell 的副作用实际上发生在语言范围之外,这就是为什么它是纯粹的。