1)函数或者表达式修改了它的SCOPE之外的状态
2)函数或者表达式除了返回语句外还与外部世界或者它所调用的函数有明显的交互行为
int se(int * p){
(*p)++;
return *p;
}
以及caller:
void foo(){
int k = 1;
int * p = &k;
se(p);
}
几乎可以按照定义逐字匹配,se(int *)函数修改了域外(CALLER FUNCTION)的变量k的状态(++),所以se(int *)函数具有副作用
在2)中可以认为外部世界是相对于程序不确定的外部因素,如计算机实体(磁盘,内存条),用户输入行为等。
副作用是区别函数式编程语言和当今主流的面向对象/过程式编程语言的显著特征。在面向对象/过程式中变量(VARIABLE)是可以改变的量,一如代码实现。
int a = 1;
a = 2;
change(a,3);
对于"="的行为我们都知道:
哦,这是给变量一个值,如果我发现这个值不能满足我的需求还可以重新给它值。
赋值运算符提供了面向对象/过程式编程语言绝大部分的副作用。如果懂得这一点在面对函数式语言的"="的时候就不会惊恐,函数式之所以声称(几乎)无副作用就是因为它的"="有别于传统观念上的赋值行为,在函数式中"="是匹配(Match)运算符,对于没有进行绑定的变量第一次使用匹配运算符发生绑定行为,如erlang中
X = atom. %ok
X = newAtom. %error
可以看到当变量X第一次使用"="与atom绑定之后对其使用"="就会发生匹配行为而不是重新绑定,作为更强烈的论证可以看到
atom = X. %ok, match variable with atom
绑定后的X可以放到匹配运算符的左边。
匹配行为不与外部世界发生交互,它不会修改外部世界的任何状态,所以这也解释了函数式编程语言为什么几乎无副作用,不说"完全"是因为只要有IO就有副作用,没有IO的语言..我没见过.
正是因为(几乎)无副作用使得函数式编程语言在数学定义证明,并发处理等方面有天然的优势。