函数副作用
在计算机科学中,函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。
函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,并降低程序的可读性。严格的函数式语言要求函数必须无副作用。
下面是函数的副作用相关的几个概念,纯函数(Pure Function)、非纯函数(Impure Function)、引用透明(Referential Transparent)。
纯函数
纯函数(Pure Function)——输入输出数据流全是显式(Explicit)的。
显式(Explicit)的意思是,函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。
非纯函数
如果一个函数通过隐式(Implicit)方式,从外界获取数据,或者向外部输出数据,那么,该函数就不是纯函数,叫作非纯函数(Impure Function)。
隐式(Implicit)的意思是,函数通过参数和返回值以外的渠道,和外界进行数据交换。比如,读取全局变量,修改全局变量,都叫作以隐式的方式和外界进行数据交换;比如,利用 I/O API(输入输出系统函数库)读取配置文件,或者输出到文件,打印到屏幕,都叫做隐式的方式和外界进行数据交换。
引用透明
引用透明(Referential Transparent)的概念与函数的副作用相关,且受其影响。如果程序中任意两处具有相同输入值的函数调用能够互相置换,而不影响程序的动作,那么该程序就具有引用透明性。它的优点是比非引用透明的语言的语义更容易理解,不那么晦涩。纯函数式语言没有变量,所以它们都具有引用透明性。
范例
f(x)
{
return x + 1
}
f(x)函数就是纯函数。
a = 0
q(x) {
b = a
}
q(x)访问了函数外部的变量。q(x)是非纯函数。
p(x){
print"hello"
}
p(x)通过I/O API输出了一个字符串。p(x)是非纯函数。
c(x) {
// 假設readConfig()函數為I/O API的函數
data = readConfig() // 读取配置文件
}
c(x)通过I/O API读取了配置文件。c(x)是非纯函数。
纯函数内部有隐式(Implicit)的数据流,这种情况叫做副作用(Side Effect)。上述的I/O,外部变量等,都可以归为副作用。因此,纯函数的定义也可以写为"没有副作用的函数"。
I/O API 可以看作是一种特殊的全局变量。文件、屏幕、数据库等输入输出结构可以看作是独立于运行环境之外的系统外全局变量,而不是应用程序自己定义的全局变量。
特殊的函数副作用
上述只讨论了一般的情况,还有一种特殊的情况,我们没有讨论。有些函数的参数是一种 In/Out 作用的参数,即函数可能改变参数里面的内容,把一些信息通过输入参数,夹带到外界。这种情况,严格来说,也是副作用。也是非纯函数。 比如下面的函数。
process(context) {
a = context.getInfo()
result = calculate(a)
context.setResult(result)
}
纯函数的优点
纯函数的好处主要有几点: