2011-04-04 27 views
79

我正在通過Write Yourself a Scheme in 48 Hours(我達到約85小時)工作,我已經開始了關於Adding Variables and Assignments的部分。這一章有一個很大的概念上的跳躍,我希望它在兩個步驟中完成了重構,而不是直接跳到最後的解決方案。無論如何...State,ST,IORef和MVar之間的差異

我已經迷失了許多不同的類似乎服務於相同的目的:State,ST,IORefMVar。文中提到了前三個,而最後一個似乎是關於前三個StackOverflow問題的回答。它們似乎都在連續的調用之間進行狀態。

這些都是什麼,它們又有什麼不同?


特別是這些句子是沒有意義的:

相反,我們使用了一個名爲狀態的線程功能,讓哈斯克爾管理聚集狀態對我們來說。這讓我們可以像使用其他編程語言那樣處理可變變量,使用函數來獲取或設置變量。

的IOREF模塊,您可以使用狀態變量的IO單子內。

這一切都使得行混淆 - 爲什麼第二個IORef?如果我會寫type ENV = State [(String, LispVal)],會發生什麼?

回答

103

國家單子:可變狀態的模型

國家單子是與國家計劃純粹的功能性的環境,用一個簡單的API:

  • 得到

the mtl package的文檔。

狀態monad通常用於在單個控制線程中需要狀態。它在實現中實際上並不使用可變狀態。相反,程序由狀態值參數化(即狀態是所有計算的附加參數)。該狀態似乎只在單個線程中發生變化(並且不能在線程之間共享)。

的ST單子和STRefs

的ST單子是IO單子的受限表親。

它允許任意可變狀態,實現爲機器上的實際可變內存。由於rank-2類型參數阻止取決於可變狀態的值逃離本地作用域,因此API在無副作用程序中變得安全。

因此它允許在純粹的程序中控制可變性。

通常用於可變數組和其他變異的數據結構,然後凍結。這也是非常有效的,因爲可變狀態是「硬件加速」。

主要API:

  • Control.Monad.ST
  • runST - 開始一個新的記憶效應計算。
  • STRefs:指向(局部)可變單元的指針。
  • 基於ST的陣列(如矢量)也很常見。

認爲它是IO單子的危險性較低的兄弟姐妹。或者IO,在那裏你只能讀寫內存。

IOREF:STRefs在IO

這些是STRefs(見上文)在IO單子。他們沒有STRefs關於地方的安全保證。

MVars:用鎖

像STRefs或IORefs,但附有一把鎖,從多個線程安全併發訪問IORefs。使用atomicModifyIORef(比較和交換原子操作)時,IORefs和STRefs在多線程設置中僅是安全的。 MVars是安全共享可變狀態的更一般的機制。

通常,在Haskell中,通過STRef或IORef使用MVars或TVars(基於STM的可變單元格)。

+3

在MVars和T中的M是什麼?我猜「可變」,「交易」。有趣的是,ST如何表示狀態線程。 – CMCDragonkai 2015-02-23 10:38:05

+7

爲什麼你說'MVar'應該比'STRef'更受歡迎? 'STRef'保證只有一個線程可以改變它(並且不會發生其他類型的IO) - 當然,如果我不需要併發訪問可變狀態,那肯定會更好? – 2015-03-05 21:39:22

+0

@CMCDragonkai我一直認爲M代表互斥體,但我無法在任何地方找到它。 – 2017-10-09 13:25:00

35

好吧,我將從IORef開始。 IORef提供了一個在IO monad中可變的值。它只是對某些數據的引用,並且與任何引用一樣,都有一些函數允許您更改其引用的數據。在Haskell中,所有這些函數都在IO中運行。你可以把它想象成一個數據庫,文件或其他外部數據存儲 - 你可以獲取和設置數據,但這樣做需要通過IO。 IO完全有必要的原因是因爲Haskell是純粹的;編譯器需要一種方法來了解在任何給定時間參考指向的數據(閱讀sigfpe's "You could have invented monads" blogpost)。

MVar s與IORef基本相同,除了兩個非常重要的區別。 MVar是一個併發原語,因此它被設計用於從多個線程訪問。第二個區別是MVar是一個可以是滿的或空的盒子。因此,IORef Int總是有Int(或底部),MVar Int可能有Int或它可能爲空。如果一個線程試圖從一個空的MVar讀取一個值,它將會阻塞直到MVar被填充(被另一個線程填充)。基本上,MVar a相當於IORef (Maybe a),它具有對併發有用的額外語義。

State是一個monad,它提供可變狀態,不一定與IO。事實上,它對純粹的計算特別有用。如果您有一個使用狀態但不是IO的算法,則State monad通常是一個優雅的解決方案。

還有一個monad變壓器版本的狀態,StateT。這經常用於保存應用程序中的程序配置數據或「遊戲世界狀態」類型的狀態。

ST略有不同。 ST中的主要數據結構是STRef,它與IORef類似,但具有不同的monad。 monad使用類型系統欺騙(「狀態線程」文檔提到)來確保可變數據無法轉義monad;也就是說,當你運行ST計算時,你會得到一個純粹的結果。 ST很有趣的原因是它是一個像IO這樣的原始monad,允許計算對bytearrays和指針執行低級操作。這意味着ST可以在對可變數據使用低級操作時提供純粹的接口,這意味着它非常快速。從程序的角度來看,就好像ST計算在帶有線程本地存儲的單獨線程中運行。

17

其他人也做了核心的東西,而是要回答這個直接的問題:

這一切使得線型ENV = IORef [(String, IORef LispVal)] 混亂。爲什麼第二個IORef?如果我做的是type ENV = State [(String, LispVal)], 會破壞什麼?

Lisp是一種具有可變狀態和詞法範圍的函數式語言。想象一下你已經關閉了一個可變變量。現在你已經有了一個引用這個變量的函數 - 例如(在haskell風格的僞代碼中)(printIt, setIt) = let x = 5 in (\() -> print x, \y -> set x y)。你現在有兩個功能 - 一個打印x,一個設置它的值。當你評估printIt,你要查找的名在其中printIt定義初始環境x的,但你要查找的這個名字在其中printIt稱爲環境勢必(之後setIt可能已被調用任意次數)。

有兩種方法可以讓兩個IORefs做到這一點,但你肯定需要的不僅僅是你提出的後一種類型,它不允許你改變名稱以詞法作用域方式綁定的值。谷歌的「funargs問題」爲很多有趣的史前史。

相關問題