13

我需要幫助,讓我的頭腦圍繞當前的OOP狀態概念和它在Haskell或Clojure等函數式語言中的做法之間的區別。函數式編程:狀態與再調用

要使用一個陳舊的例子,假設我們正在處理簡化的銀行帳戶對象/結構體/任何。在OOP語言中,我會有一些類持有對BankAccount的引用,這些BankAccount將具有諸如利率之類的實例變量,以及像setInterestRate()這樣的方法,它們會更改對象的狀態並且通常不會返回任何內容。在說Clojure時,我會有一個銀行帳戶結構(一個榮耀的散列表),以及一些特殊函數,它們需要一個銀行帳戶參數和其他信息,並返回一個新的結構。因此,我不用改變原始對象的狀態,而是通過所需的修改返回新的對象。

那麼...我該怎麼處理它呢?覆蓋引用舊銀行帳戶的任何變量?如果是這樣,這是否比改變狀態的面向對象方法有優勢?最後,在這兩種情況下,似乎都有一個變量引用具有必要更改的對象。儘管我受到了阻礙,但對於發生的事情我只有一個模糊的概念。

我希望這是有道理的,謝謝你的幫助!

+1

此視頻是關於此主題的「必備手錶」:http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey – mikera 2012-01-06 03:47:49

回答

11

在純粹的功能風格中,你永遠不會覆蓋任何變量。

類比將是物理學中的時空。如果你認爲世界是三維的,那麼物體就沒有固定位置 - 它們會隨着時間的推移而移動。爲了使數學對物理世界產生影響,我們因此增加了時間維度並考慮了特定時間的各種性質的值。在這樣做的過程中,我們將研究的對象變爲常量。同樣,在編程中,通過使用不可變的值來處理概念上的簡單性。在現實世界中具有身份的對象可以被建模爲一系列不可變的值(對象的狀態在不斷增加),而不是作爲單一值發生變化。

當然,如何將值序列與「對象標識」相關聯的細節可能有點多毛。 Haskell有Monads讓你模擬狀態。 Functional Reactive Programming是一個更純粹的功能更新在世界上建模對象的文字嘗試,我認爲這是一個非常有前途的編程方向。

我會注意到,與Haskell不同,Clojure不是純粹的,您可以按照您的建議更新變量。如果您只是在較高層次上更新一些變量,那麼您仍然可能會享受到函數式編程的許多簡單概念優點。

4

那麼......我該怎麼處理它呢?覆蓋引用舊銀行帳戶的任何變量?

如果是這樣,這是否有過狀態改變OOP方法的優勢?

假設您在該結構上執行的任何操作的計算需要很長時間,並且中途發生了一些事情,並且需要恢復到原始結構或計算引發錯誤。根據你給我的OO的解釋(使用一個引用,因爲你可以有一個不變的OO語言)數據可能會被破壞 - 除非有足夠的信息來自失敗的函數調用,否則它是未知的,並且讓我們建議它失敗厲害。在功能性方法中,您肯定知道您的原始數據結構是正確的 - 因爲您最初製作了副本。

在多線程應用程序中擴展此場景。我們可以確保沒有其他人使用我們的數據結構,因爲我們都有自己的版本。

此外,我們可以使用我們正在複製的其他結構中的數據節省空間。一個典型的例子是當一個元素添加到列表頭部時。如果我們有一個指向第二個元素的指針和一個指向第一個元素的指針,那麼我們可以只用第一個元素的大小引用這兩個列表(見下文)。沒有不變性,我們不能保證這一點。

 b__ 
      | 
a -> [6|] -+-> [5|] -> [4|] -> [3|] -> [2|] -> [1|x] 
1

看看Haskell中,這是一個純粹的功能性語言,它沒有重新分配無論如何,還有沒有其他的副作用:爲了做IO,在IO monad構建它實際上取代了真實世界與一個新的世界實例,例如,在控制檯中顯示新的文本。

8

假設在面向對象的世界裏,你有一個循環,並且一次又一次地修改這些銀行賬戶以響應請求。假設您有一個完整的賬戶組合,而且這些賬戶都有類型組合。然後在Haskell你會寫一個純函數

updatePortfolio :: Request -> Portfolio -> Portfolio 

而且你的主循環可能會從標準輸入讀取請求,並保持你的投資組合最新的。 (這個例子是沒有多大用處,除非你可以寫組合爲好,但它是簡單的。)

readRequest :: IO Request -- an action that, when performed, reads a Request with side effects 

main :: Portfolio -> IO() -- a completely useless program that updates a Portfolio in response to a stream of Requests 

main portfolio = do req <- readRequest 
        main (updatePortfolio req) 

,現在我希望你看看發生了什麼,以你的可變狀態:在一個典型的功能性程序,狀態更改作爲參數傳遞給函數。當狀態改變時,你進行一個新的函數調用。該調用處於尾部位置(您可以查找'正確的尾部調用'),因此它不會使用任何其他資源,事實上,當編譯器生成彙編代碼時,它會生成一個循環,並且它將保持指向不斷變化的投資組合。

這是一個非常玩具的例子,但我希望它給你一點功能語言的味道。