計算機編程中經常提到的副作用,具體指的是什麼?有什麼定義嗎?

在Scala編程中,多次提到副作用,尤其IO部分,那麼副作用具體指的是什麼?Java和CPP 一些教材中,也會提到這個詞,但是只是知道i++ 屬於副作用,那麼 為什麼它屬於副作用呢?


副作用就是對所處的環境有改變。

既然有改變多次調用有副作用的語句或函數,結果不一定保持一致。而如果是無副作用的語句或函數,調用多次結果是一致的。


最簡單的理解:

一個函數的調用是沒有副作用的(一般的),但一個方法的調用未必。

# FUNCTION
def f(x,y)
x+y
end

# METHOD
def m(x,y)
if self.x&>0
self.x=x
end
x+y
end

函數的調用只是獲取了輸入,進行計算,併產生一個結果。這裡並沒有副作用。

但方法可能會導致當前上下文的變化。在上面的例子中,將導致對self.x的賦值。而方法依賴當前環境(一般總是依賴,不然就應該是一個函數).


瀉藥

wikipedia 鎮樓

In computer science, a function or expression is said to have a side effect if it modifies some state or has an observable interaction with calling functions or the outside world. For example, a particular function might modify a global variable or static variable, modify one of its arguments, raise an exception, write data to a display or file, read data, or call other side-effecting functions. In the presence of side effects, a program"s behaviour may depend on history; that is, the order of evaluation matters. Understanding and debugging a function with side effects requires knowledge about the context and its possible histories.

Side effect (computer science)

副作用的對立面是純函數。

純函數的返回值是由參數決定的,例如 Math.floor() ,給定參數,所能得到的返回值是一定的。

而副作用就是,一個函數它的行為不只依賴於參數,也不只是根據參數給出返回值。

例如 redux 里的 reducer ,就是利用了純函數,把複雜的程序狀態管理起來,變得可預期、可擴展、可維護、可測試。


副作用主要來源於賦值語句。

如果賦值語句修改了內存原有的值,就會產生副作用。i++,這個就是修改了內存中i的值。

如果賦值語句不在內存原有值的基礎上進行修改,而是重新申請一塊內存保存運算結果,避免了對內存原有值的修改。這種也是函數式編程中變數不可以重新賦值的原因。高大上的說法就是immutable。


副作用是指函數或表達式不僅產生一個值,還修改了一些狀態(其它變數)或者與外界的環境有明顯的交互。比如,一個函數可能修改全局變數或static變數(C/C++),修改實參,引發異常,寫文件(I/O)、保存至資料庫或者調用其它有副作用的函數等等。副作用是程序與外界交互的一種常用方式。

高級語言中可以認為只要修改了程序所在環境的內存狀態或IO操作就會產生副作用(彙編的話寄存器狀態的改變也會算作副作用)。

命令式語言(如C/C++)會大量使用副作用,最典型的是常用的賦值語句。相反,函數式編程語言(如Haskell)則很少使用副作用甚至避免使用。像Scala這種命令式與函數式混合的語言則相對靈活些,不過一般還是要求函數或方法遵從"no side effects"的約定。

這裡重點提下C/C++,因為效率和優化考慮,其specification允許編譯器廠商在編譯時對程序的執行或計算順序進行優化,那麼程序的行為就會依賴於副作用的產生順序,不同編譯器的不同實現可能會導致同一程序出現不同的運行結果,比如常見的Undefined Behavior。所以C/C++引入了sequence point,對於任意一個sequence poin,在它之前的所有副作用都已經完成,在它之後的所有副作用都尚未發生,但在兩個sequence poin之間,表達式的求值和副作用的順序是不確定的,比如printf("%d %d", i++, ++i)在參數初始化完成之後第一條語句調用之前是沒辦法保證i++和++i哪個先執行的。而Java就不存在以上這種問題。


這種說法一般用在函數調用上,指函數對除了返回值之外的其他東西的修改。


++(i++)+8)+(--j)*((++k)+(--b)+(c--)/((++i)--))

這種式子我估計本人也很難看懂吧


覺得副作用這個詞很形象準確啊,就跟吃藥的副作用概念差不多啊,吃個肚子疼的葯,治好肚子疼是真正的作用,如果引起了厭食啊,頭暈啊就是副作用了,編程里語句或者函數提供一個返回值是真正的作用,如果除了返回值之外做的那些事就是副作用,比如i++除了表達式的值之外還修改了i,向IO輸出東西修改了IO的狀態。如果函數里有個全局變數只讀不寫就沒副作用,如果修改了全局變數就是副作用。


抖個機靈,最大的副作用是沒對象

題主所說的還有一個go語句


改了調用者沒有預期要改的東西就是副作用


推薦閱讀:

後XP 時代的一些思考
哪款經修改的安卓系統適合中老年人使用?
學堂在線和網易雲課堂的操作系統課程比較?
為什麼清華北大中科院的人那麼出色,但是中國還是不能很順利的造出一個可使用的CPU,或者操作系統?
內存戰爭

TAG:操作系統 | 編程語言 | Java | Scala |