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的語言..我沒見過.
正是因為(幾乎)無副作用使得函數式編程語言在數學定義證明,並發處理等方面有天然的優勢。