簡體   English   中英

函數式編程中不可變數據的問題

[英]Problems with Immutable Data in Functional Programming

我是函數式編程的新手。 我所理解的是函數式編程是使用純函數編寫代碼而不改變數據的價值。

我們不需要改變變量的值,而是在需要更新變量時在函數式編程中創建新的變量。

假設我們有一個變量x ,它表示程序發出的HTTP請求總數。 如果我們有兩個線程,那么我希望線程在任何線程發出HTTP請求時遞增x 如果兩個線程都生成變量x的不同副本,那么它們如何同步x的值。 例如:如果線程1發出10個HTTP請求而線程2發出11個HTTP請求,那么它們將分別打印10和11但是我將如何打印21。

我可以為clojure案例提供答案。 clojure ,如果您需要協調對共享狀態的訪問,那么語言中的構造將用於處理這些情況。

在這種情況下,您可以使用atom來保存該值。 atom所做的改變是原子的,並且將通過clojure的STM樂觀地進行。 原子是clojure的參考類型之一。 原子本質上是對一個值的引用,它可以通過原子的變異函數以受控的方式隨時間變化。

有關原子和其他引用類型的更多信息,請參閱clojure 文檔

我將解決Haskell部分。 MVar是線程的通信機制之一。 這是Simon Marlow的書(該程序不言自明)中的一個例子:

main = do
  m <- newEmptyMVar
  forkIO $ do putMVar m 'x'; putMVar m 'y'
  r <- takeMVar m
  print r
  r <- takeMVar m
  print r

上述程序的輸出將是:

'x'
'y'

您可以在上面的示例中看到如何在線程之間共享變量mMVar值。 您可以在本書中了解有關這些技術的更多信息。

我還將討論Haskell部分。

首先,我想澄清一些事情:

我們不需要改變變量的值,而是在需要更新變量時在函數式編程中創建新的變量。

那不太准確。 我們在需要時在FP中創建新的“變量”,而不是在需要改變現有變量時。 當我們做你所描述的事情時,我們甚至不會考慮突變; 我們可能只是認為我們正在創造一個類似於我們所擁有的新價值。

你用線程描述的內容有點不同。 你實際上是在尋找副作用(增加一個計數器)。 Haskell是純粹的,不會讓你在不明確的情況下拋出副作用。 因此,在這種情況下,您將需要求助於引用類型/可變單元格。 最簡單的一個叫做IORef ,在這個意義上它就像一個變量; 您可以分配值,讀取當前值,等等。

所以,正如你所看到的,當你在尋找這些東西時,你真的只有一個櫃台副本。

以上是我的答案的本質,但你已經具體詢問了線程,所以我也會回答這個問題。
IORef實際上並不是線程安全的。 所以有建議的MVar 它們不像常規變量,但它們很接近,它們可以優雅地完成工作。 一般而言,松散地說:它們抽象變量和鎖定。 我想你可能會覺得TVar更容易。 它們的行為類似於IORef /變量,只有兩者不同,它們都是線程安全的; 您可以將它們的操作組合成一個操作,並且對它們執行的任何操作都是以原子方式完成的(STM)。

順便說一句,你可能會找到完全避免狀態的方法,這是非常鼓勵的。 例如,您可以讓兩個線程執行一個異步遞歸函數,該函數通過參數記住已經發出了多少請求,然后將其作為返回值。 請求總數是所有線程返回的請求總和。 這可以避免計數器上的副作用,但它只能在線程完成時產生結果。 這是非常有限的,所以有時你可能想要那種副作用。

好吧,我會在保持狀態時嘗試提​​供更一般的解釋,因為我認為這是你真正想知道的。

通常,您可以通過遞歸完成相同的操作,例如,如果您有以下功能:

somefun ()->
   somefun(0).
somefun (X) ->
  perform_http_request(),
  if(something!=quit)
     somefun(X+1)
end function.

generate_thread(0, Accumulator) ->
      Accumulator;
generate_thread(X, Accumulator) ->
      Y = somefun(),
      NewAccumulator = add_to_accumulator(Y),
      generate_thread(X-1, NewAccumulator).

我只是匆匆輸入這個,這是一個非常通用的解釋(你將不能直接使用這個代碼)但是你認為你可以發現你真的沒有這里的可變性......這個功能將在所有時候完成線程完成他們的處理,現在你可以做的實際線程同步取決於你選擇的語言和不同的語言有不同的處理並發和“線程”的方式..我建議你看看如果你是Erlang並發,因為它有一個非常好的並發模型imo。

無論如何,最后你可以將累加器中返回的所有值相加並顯示出來,順便看看foldl和foldr函數。

我自己不是大師,但我想你可能會錯過了解。

如果不保持狀態,就無法創建一個非常有用的程序 需要說明這種或那種方式。 FP的目標不是避免狀態, 而是要控制狀態的使用

以這種方式看待,您的狀態應該與數據庫條目一樣孤立和安全。 如果你像對待數據庫那樣對待狀態,我想你會沒事的

這意味着,

  • 你不會有一些登錄(inc count) 你寧願有一個函數increment-count! 這將安全地更新計數。 注意! ,這意味着副作用。
  • 您將沒有依賴於副作用的代碼 相反,你將依賴於期望從他們的參數獲得一切的函數。 除非他們絕對要依賴國家。 就像更新計數一樣,這是對州的無可否認的要求。
  • 你的第一選擇應該是避免國家。 將它傳遞給函數變得不可能時,您將創建正確更新的狀態。
  • 像處理外部API一樣處理狀態。 遠程你必須使用某種協議來訪問。

我希望這是有道理的。

我將地址Erlang部分。 即使在Erlang中也沒有神奇的同步,某個地方的某個人必須處理同步。 只是Erlang具有不變性(也就是沒有變量)的好處,有助於防止並發編程中的常見同步錯誤。 像gen_server這樣的Erlang / OTP已經擁有管理狀態的基礎設施。 實際上,gen_server是單線程的,它接收的任何消息都在郵箱中排隊。 這是一個關於Erlang的消息並發的鏈接。 Erlang如何同時處理訪問郵箱

在原始帖子的情況下,要掛起一個http請求計數器,您可以使用單個gen_server OTP(Erlang)。 你會驚訝於它可以處理多少吞吐量。 如果單個gen_server吞吐量確實不足,則可以使用分層gen_server來聚合計數。 Erlang / OTP附帶了一組運行時API,可以實時測量性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM