2012-08-24 30 views
8

我明白函數式編程本身的不同概念:副作用,不變性,純函數,參照透明性。但我無法將它們連在一起。例如,我有以下問題:不同函數式編程概念之間的關係

  1. ref。是什麼關係?透明度和不變性。一個人暗示另一個人?

  2. 有時副作用和不變性可以互換使用。這是對的嗎?

+0

您忘記了「冪等性」:P – mergeconflict

回答

2

第一個「否」 - 一個意味着另一個,但不是相反,並且第二個是合格的「是」。

  1. An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program」。 不可變輸入表明表達式(函數)將始終計算爲相同的值,因此是引用透明

    但是,(mergeconflict已經好心糾正我)關於引用透明並不一定要求不可變性

  2. 根據定義,副作用功能的一個方面;這意味着當你調用一個函數,它會改變不變性數據的一個方面;它不能改變。 調用這樣的函數確實意味着不會有副作用。 (在Scala中,這僅限於「不改變不可變對象」 - 開發人員有責任和決定)。

    雖然副作用不變性並不意味着同樣的事情,他們是一個功能密切相關的方面和功能應用到數據。在輸入到函數的範圍可以包括元素比那些通過其它 -

由於Scala是不是純粹的功能的編程語言,必須考慮這樣的陳述爲「不可改變的輸入」的含義時要小心作爲參數。同樣考慮到副作用。

+0

我喜歡您在(2)中的說明,即副作用與函數相關,而不變性與數據相關,但我認爲您的解釋在(1)中是錯誤的。參考透明度並不直接與可變性相關,而是與環境相關,如您所引用的引用所示。 – mergeconflict

+0

@mergeconflict感謝您的更正,並在您的回覆中的細節!回答相應調整。 –

1

它,而取決於你使用(可以有不同意見,例如參見Purity vs Referential transparency)的具體定義,但我認爲這是一個合理的解釋:

引用透明和「純度」是函數/表達式的性能。函數/表達式可能會或可能不會有副作用。另一方面,不變性是物體的屬性,而不是功能/表達。

引用透明度,副作用和純度密切相關:「純」和「引用透明」是等價的,這些概念相當於沒有副作用。

不可變對象可能有不透明的方法:這些方法不會改變對象本身(因爲這會使對象變爲可變的),但可能有其他副作用,如執行I/O或操作它們(可變)參數。

8

這個問題,需要一些特別挑剔的答案,因爲它是如何定義常用的詞彙。

首先,函數是輸入的「域」和輸出的「範圍」(或共域)之間的一種數學關係。每個輸入都會產生一個明確的輸出。例如,整數加法功能+接受域中Int x Int輸入和範圍Int產生輸出。

object Ex0 { 
    def +(x: Int, y: Int): Int = x + y 
} 

鑑於xy任何值,顯然+總是會產生相同的結果。這是一個功能。如果編譯器非常聰明,它可以插入代碼來緩存每個輸入對的該函數的結果,並將緩存查找作爲優化來執行。這在這裏顯然是安全的。

的問題是,在軟件中,術語「功能」已經有所濫用:雖然函數接受參數,並在他們的簽名中聲明的返回值,他們還可以讀取和寫入一些外部環境。例如:

class Ex1 { 
    def +(x: Int): Int = x + Random.nextInt 
} 

我們不能認爲這是一個數學函數了,因爲對於x+給定值會產生不同的結果(根據隨機值,不出現任何地方+的簽名)。如上所述,不能安全地緩存+的結果。所以現在我們有一個詞彙的問題,這是我們通過說Ex0.+解決,而Ex1.+不是。

好了,因爲我們現在已經接受了某種程度的雜質,我們需要定義什麼樣的雜質我們談論!在這種情況下,我們說不同的是,我們可以緩存Ex0.+同其投入xy相關的結果,而我們不能緩存Ex1.+「與它的輸入x相連的S結果。我們用來描述緩存能力的術語(或者更恰當地說,函數調用與其輸出的可替換性)是參考透明度

所有純函數是引用透明的,但有些引用透明功能不是純的。例如:

object Ex2 { 
    var lastResult: Int 
    def +(x: Int, y: Int): Int = { 
    lastResult = x + y 
    lastResult 
    } 
} 

這裏,我們沒有從任何外部環境讀書,和Ex2.+對任何輸入xy產生的價值始終緩存,如Ex0。這是引用透明的,但它確實有一個副作用,它將存儲函數計算的最後一個值。其他人可以稍後再來,並抓住lastResult,這會讓他們對Ex2.+發生的事情有些偷偷摸摸!

一個側面說明:你也可以認爲Ex2.+引用透明,因爲雖然緩存是關於函數的結果安全的,副作用是默默地在高速緩存的情況下,忽略「擊中。」換句話說,如果副作用很重要(因此Norman Ramsey's comment),引入緩存會改變程序的含義!如果你喜歡這個定義,那麼一個函數必須是純粹的,以便引用透明。現在

,這裏有一點要觀察的是,如果我們在相同的輸入行調用Ex2.+兩次或更多次,lastResult不會改變。調用方法n次的副作用相當於只調用該方法一次的副作用,所以我們說Ex2.+冪等性。我們可以改變它:

object Ex3 { 
    var history: Seq[Int] 
    def +(x: Int, y: Int): Int = { 
    result = x + y 
    history = history :+ result 
    result 
    } 
} 

現在,每一個我們稱之爲Ex3.+,歷史變化的時間,所以功能不再冪等。

好吧,到目前爲止回顧:a pure函數既不讀取也不寫入任何外部上下文。它是引用透明副作用免費。從外部上下文讀取的函數不再是透明的,而寫入外部上下文的函數不再具有副作用。最後,當用相同的輸入多次調用具有與僅調用一次相同的副作用時,函數被稱爲冪等性。請注意,沒有副作用的函數(如純函數)也是冪等的!

那麼如何可變性不變性發揮所有這一切?那麼,回頭看看Ex2Ex3。他們介紹可變的var s。 Ex2.+Ex3.+的副作用是改變它們各自的var!所以可變性和副作用是攜手並進的。僅對不可變數據進行操作的函數必須是無副作用的。它可能還不是純粹的(也就是說,它可能不是透明的),但至少它不會產生副作用。

對此的邏輯後續問題可能是:「純功能風格有什麼好處?」 )

+0

非常好的'純'的描述,謝謝你。不過,我不同意你的例子'Ex2'的參考透明度,儘管:這個副作用對於程序的其他部分是可見的(因爲它可能讀取'lastResult'),這意味着例如對這個方法應用記憶會改變結果/程序的可觀察行爲。這使得這種方法不具有透明性 - 我不知道任何不同意的定義。 –

+0

您可能會想到的灰色區域是私有緩存,對於程序的其餘部分無法訪問:將中間結果存儲在此類緩存中的方法確實有副作用(填充緩存),但應用例如記憶不會改變程序的結果。這種方法是否被認爲是「引用透明」的確是有爭議的。 –

+0

正如@ArnoutEngelen已經說過的那樣,你的意思是與引人注意的<->副作用有點混淆。你能否提供一個透明但沒有副作用且不純的例子? – sschaef