函数式编程语言是否会带来副作用?


10

根据Wikipedia 所说,声明性的函数式编程语言禁止副作用。通常,声明式编程会尝试最小化或消除副作用。

同样,根据维基百科,副作用与状态变化有关。因此,从某种意义上说,函数式编程语言实际上消除了副作用,因为它们不保存任何状态。

但是,此外,副作用还有另一个定义。副作用

除了返回值之外,还可以与其调用函数或外界进行交互。例如,特定函数可能会修改全局变量或静态变量,修改其参数之一,引发异常,将数据写入显示或文件,读取数据或调用其他副作用函数。

从这种意义上说,函数式编程语言实际上会带来副作用,因为有无数的函数实例影响它们的外部世界,调用其他函数,引发异常,写入文件等。

那么,最后,函数式编程语言是否允许副作用?

或者,我不明白什么是“副作用”,因此命令式语言允许它们,而声明式语言则不允许。根据以上内容以及所得到的内容,没有一种语言能够消除副作用,因此我可能遗漏了一些有关副作用的信息,或者Wikipedia定义的定义不正确。

Answers:


26

函数式编程包括许多不同的技术。有些技术可以带来副作用。但是一个重要的方面是方程式推理:如果我用相同的值调用一个函数,我总是会得到相同的结果。因此,我可以将函数调用替换为返回值,并获得等效的行为。这使得对程序进行推理变得更容易,尤其是在调试时。

如果该功能有副作用,那将不成立。返回值不等于函数调用,因为返回值不包含副作用。

解决的办法是停止使用的效果和编码这些效果在返回值中。不同的语言具有不同的效果系统。例如,Haskell使用monad来编码某些效果,例如IO或状态突变。C / C ++ / Rust语言的类型系统可以禁止某些值的突变。

在命令式语言中,print("foo")函数将打印某些内容,但不返回任何内容。在像Haskell这样的纯函数式语言中,print函数还接受表示外部世界状态的对象,并在执行此输出后返回表示状态的新对象。类似的东西newState = print "foo" oldState。我可以根据需要从旧状态创建尽可能多的新状态。但是,主功能只能使用一个。因此,我需要通过链接功能对多个动作的状态进行排序。要打印foo bar,我可能会说类似print "bar" (print "foo" originalState)

如果未使用输出状态,则Haskell不会执行导致该状态的操作,因为它是一种惰性语言。相反,这种懒惰是可能的,因为所有效果都被明确编码为返回值。

请注意,Haskell是使用此路由的唯一常用功能语言。其他功能语言,包括 Lisp家族,ML家族和诸如Scala之类的新功能语言不鼓励使用,但仍会产生副作用-它们可以称为命令性功能语言。

为I / O使用副作用可能很好。通常,I / O(除了日志记录)仅在系统的外部边界进行。您的业​​务逻辑中不会发生任何外部通信。这样就可以以纯风格编写软件的核心,同时仍在外壳中执行不纯的I / O。这也意味着核心可以是无状态的。

无状态具有许多实际优势,例如增加了合理性和可伸缩性。这对于Web应用程序后端非常流行。任何状态都保留在共享数据库的外部。这使负载平衡变得容易:我不必将会话停留在特定的服务器上。如果我需要更多服务器怎么办?只需添加另一个,因为它使用的是同一数据库。如果一台服务器崩溃怎么办?我可以在另一台服务器上重做任何待处理的请求。当然,数据库中仍然存在状态。但是我已经将其明确并提取出来,并且如果需要,可以在内部使用纯函数方法。


感谢您的详细回答。我得出的结论是,由于等式推理,副作用不会影响功能值,这就是为什么“功能语言不允许/最小化副作用”的原因。函数值中嵌入的效果会影响并更改曾经保存的状态,或者保存在程序核心之外的状态。另外,I / O发生在业务逻辑的外部边界。
Codebot

3
@codebot,我认为不是。如果正确实现,则函数编程的副作用应反映在函数的返回类型中。例如,如果某个函数可能失败(如果不存在特定文件或无法建立数据库连接),则该函数的返回类型应封装该失败,而不是使该函数引发异常。看一下面向铁路的编程示例(fsharpforfunandprofit.com/posts/recipe-part2)。
艾伦·艾希巴赫

“ ...它们可以称为命令式功能语言。”:Simon Peyton Jones写道:“ ... Haskell是世界上最好的命令式编程语言。”
乔治

5

没有编程语言可以消除副作用。我认为最好说声明性语言包含副作用,而命令性语言则没有。但是,我不太确定有关副作用的任何讨论是否会引起两种语言之间的根本区别,而且看起来确实像您要寻找的那样。

我认为这有助于举例说明差异。

a = b + c

上面的代码行几乎可以用任何语言编写,那么我们如何确定我们使用的是命令性语言还是声明性语言?在这两类语言中,该代码行的属性有何不同?

在命令式语言(C,Java,Javascript等)中,该行代码仅表示流程中的一个步骤。它没有告诉我们任何价值观念的基本性质。它告诉我们,在这一行代码之后(但在下一行之前)a将等于b加号,c但从a更大的意义上讲,它什么也没有告诉我们。

在声明性语言(Haskell,Scheme,Excel等)中,这行代码表示的内容更多。它规定之间的关系不变a,其他两个对象,它将永远是的情况下a等于bc。请注意,我将Excel包含在声明性语言列表中,因为即使bc更改值,该事实仍将a等于它们的总和。

在我看来,是使两种类型的语言有所不同的原因,而不是副作用或状态。在命令式语言中,任何特定的代码行都不会告诉您有关变量的整体含义。换句话说,a = b + c仅表示在很短的时间内a碰巧等于bc

同时,在声明性语言中,每一行代码都建立了一个基本真理,该真理将在程序的整个生命周期中存在。在这些语言中,a = b + c告诉您,无论在任何其他代码行中发生什么,都a将始终等于band 的总和c

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.