Haskell被称为“纯函数式语言”。
在这个背景下,“纯”是什么意思?对程序员有哪些影响?
Haskell被称为“纯函数式语言”。
在这个背景下,“纯”是什么意思?对程序员有哪些影响?
在一个纯粹的函数式语言中,你不能执行任何具有副作用的操作。
副作用指的是评估表达式会改变一些内部状态,这个内部状态会导致再次评估相同的表达式结果不同。在一个纯函数式语言中,你可以使用相同的参数评估相同的表达式无限次,它会始终返回相同的值,因为没有状态可改变。
例如,纯函数式语言不能有赋值运算符或进行输入/输出操作,虽然实际上,即使是纯函数式语言也经常调用不纯的库来执行I/O操作。
f(x): x + 1
是一个简单的增量函数,因为它没有任何副作用或状态,所以它是"Pure"。然而,它不是幂等的,因为f(f(x)) != f(x)
。相反,像g(x): x - x
这样的函数既是纯的又是幂等的,因为它总是返回0。 - Kartik Sreenivasan纯函数指没有副作用的函数——它接受一个值并返回一个值,没有全局状态会被函数修改。纯函数式编程语言是强制函数必须是纯函数的语言。纯函数的一些有趣后果包括:计算可以是惰性的,因为函数调用没有其他目的而是返回一个值,如果您不打算使用它的值,则不需要实际执行该函数。由于这个特性,Haskell中的递归函数和无限列表非常常见。
另一个后果是函数的评估顺序无关紧要——因为它们互相不影响,您可以按任何方便的顺序进行评估。这意味着并行编程提出的一些问题根本不存在,因为函数执行的顺序没有“错误”或“正确”之分。
IO
或其他表示效果的单子构建的程序都是无副作用的。在Erlang中,所有不使用IO诱导库或并发功能的程序都是无副作用的(这比Haskell的情况更加牵强)。相反,在ML或Scheme中,副作用可以隐藏在任何函数中。格雷厄姆·哈顿对纯函数式语言的话题有一种稍微不同但非常有趣的看法:
有时,“纯函数式”这个术语也被广泛地用来指那些可能包含计算效果但不改变“函数”概念的语言(正如函数的基本属性得以保留)。通常,表达式的求值可以产生一个“任务”,然后单独执行该任务以引起计算效果。评估和执行阶段是分开的,使得评估阶段不会损害表达式和函数的标准属性。例如,Haskell 的输入/输出机制就属于这类。
也就是说,在 Haskell 中,函数的类型为 a -> b
并且不能具有副作用。类型为 IO(a -> b)
的表达式可以具有副作用,但它不是一个函数。因此,在 Haskell 中函数必须是纯函数,因此 Haskell 是纯函数式的。
a -> IO b
,实际上它是一个仅返回一个被IO包装的值的函数。 - fuzIO
成为纯函数(编译器在谎称它是纯的)。请参见我在Joel Spolsky的回答下面的评论和链接。 - Shelby Moore IIIIO
是纯粹的(那将是非常愚蠢的)。编译器并没有说谎,除非您认为无限运行的程序是不纯的。 - Gilles 'SO- stop being evil'IO
是纯的,并获得了 5 个赞。为什么你提到了“非终止”?你是否混淆了我写关于底部类型的另一页?似乎你正在混淆一些与我在 Haskell 中呈现的 IO
单子逻辑无关的东西。请解释一下。 - Shelby Moore IIIIO a
的值的计算(Paul所指的“不可变值”)与执行这些值的具有效果性质的操作(Paul所说的“概念解释器”)。我提到非终止是因为严格来说,非终止并不是引用透明的,因为如果您至少评估一次或根本不评估它们,则程序的行为会有所不同。这与底部类型无关;它与在每种类型中包含底部值息息相关。 - Gilles 'SO- stop being evil'IO
的命令式代码包含在程序中。程序包含命令式代码,因此程序不是100%纯的。 Haskell将其隐藏起来,但它仍然在程序执行之前包含在内。就像您使用Scala编写某些部分纯粹而其他部分没有RT一样。我非常清楚每个Haskell归纳类型都由底部值填充,因为在惰性语言中,底部是一个值,而不是一个效果。 - Shelby Moore III在纯函数式代码中,不可能有任何副作用,因此测试变得更加容易,因为没有外部状态需要检查或验证。同样,由于这个原因,扩展代码也可能变得更加容易。
每当我尝试扩展/修复代码时,非明显的副作用问题让我感到困扰,已经数不清了。
main
是一个纯函数,它将来自世界的惰性流I/O响应转换为向世界发出的惰性流I/O请求。现代Haskell使用单子(monads)使这个过程更容易编程,但概念仍然相同:一个纯值描述要应用于外部不纯世界的变换。 - ephemientST
替换整个 IO
单子,你的程序仍然是纯的,而且不会有任何区别。 - ephemient