2012-07-20 118 views
11

只需從這個優秀的tutorial中瞭解State monad。但是,當我試圖向非程序員解釋時,他們有一個難題讓我難住。爲什麼國家需要價值?

如果該國的目的是模擬可變的內存,這是爲什麼狀態單子店是該類型的函數:

s -> (a, s) 

,而不是簡單:

s -> s 

換句話說,「中間」價值的需求是什麼?例如,在我們需要的情況下,我們不可能通過簡單地將狀態定義爲(state, value)的元組來模擬它嗎?

我確定我困惑的東西,任何幫助表示讚賞。

+2

我喜歡Roman的回答,但我會說'State'的目的不是模擬可變內存,而是模擬需要可變內存的計算。運行'State'的意義在於(理論上)獲得計算結果,狀態本身就是一個神器。當然,在真正的問題中,我們經常關心輸出狀態,這也是它可用的原因。 – 2012-07-21 13:06:00

回答

16

要使用命令式語言(如C)繪製並行,s -> s對應於返回類型爲void的函數,該函數被調用純粹是爲了副作用(如改變內存)。它與State s()同構。

事實上,可以編寫僅通過全局變量進行通信的C函數。但是,與C中一樣,返回函數的值通常很方便。這就是a的用途。

當然有可能對於您的特定問題s -> s是一個更好的選擇。雖然它不是Monad,但它是Monoid(當包裹在Endo中時)。因此,您可以使用<>mempty構造這些函數,這些函數對應Monad的>>=return

+0

也's - > s'不能很好地擴展。使用C並行:通過簡單地定義新的全局變量,您總是可以將額外的信息添加到全局狀態;在Haskell中,這需要改變底層類型's'。我將用一個例子來說明:讓我們有一個狀態'Stack - > Stack',我們希望收集堆棧頂部如何看待特定時刻。在C中,你可以添加新的全局變量,'堆棧 - >堆棧'你不能 - 你必須使用更具體的東西,比如'(Stack,[a]) - >(Stack,[a])' 。使用「正常」狀態就像使用少量的<-'和'return [a,b,c]一樣簡單' – Vitus 2012-07-20 17:29:54

+0

對不起,但我給了這個答案一個-1。我擔心,雖然真實且功能強大,但對於一位瞭解'狀態'的新Haskell開發人員而言,Monoid對函數的看法可能會更困惑,而不是有幫助。最初,將函數組合直接作爲函數組合(或者'Control.Category。(。)',如果你想要更通用一些)比單一的append更簡單。 – mergeconflict 2012-07-23 17:23:37

5

爲了擴大在尼克的回答有點:

s是國家。如果你所有的函數都是s -> s(狀態到狀態),你的函數將不能返回任何值。你可以將你的狀態定義爲(the actual state, value returned),但是這種狀態與state-ful函數計算的值相混淆。還有一種常見的情況是,你需要函數實際計算並返回值...

1

大概你想用do表示法中的有狀態計算嗎?

你應該問自己Monad情況會是什麼樣子的定義有狀態的計算由

newtype State s = { runState :: s -> s } 
+1

考慮如何編寫'Functor'實例可能更具啓發性。 'fmap ::(a - > b) - >狀態a - >狀態b'。雖然您可以輕鬆地轉換「輸出」狀態的結束,但您沒有足夠的信息來轉換輸入結束。 – 2012-07-20 20:59:03

+0

我原本以「實際上,Functor'實例是什麼樣子結束了答案?」但決定不包括它:) – 2012-07-20 22:52:12

2

s' -> s'相當於(a, s) -> (a, s)。在這裏,很顯然你的State將需要一個初始的a開始除了s之外。

另一方面,s -> (a, s)只需要種子s開始的東西,根本不需要a值。

因此,s -> (a, s)的類型告訴您State不像(a, s) -> (a, s)那麼複雜。 Haskell中的類型傳達大量信息。

2

如果State的目的是模擬可變的內存,這是爲什麼狀態單子店是該類型的函數:

s -> (a, s) 

,而不是簡單:

s -> s 

單元State的目的不是模擬可變內存,而是模擬均爲產生一個值有副作用。簡單地說,給定s類型的初始狀態,您的計算將產生一些類型爲a的值以及更新的狀態。

也許你的計算不會產生一個值...然後,容易:值類型a只是()。另一方面也許你的計算沒有副作用。再次簡單一點,您可能會想到您的狀態轉換函數(s -> s參數爲modify)爲id。但是你經常在同一時間處理兩者。


實際上,你可以使用getput爲相對簡單的例子:

get :: State s s  -- s -> (s, s) 
put :: s -> State() -- s -> (s -> ((), s)) 
  • get是,鑑於目前的狀態(第一s)的計算,將返回它既可作爲一個值 - 也就是計算結果 - 和「新」(未修改)狀態。

  • put是,給定一個新的狀態(第一s)和當前狀態(第二s)的計算,將簡單地忽略當前狀態。它會生成()作爲計算值(因爲它當然沒有計算任何值!)並且掛在提供的新狀態上。

0

要解決的問題是,您有一個輸入和一系列函數,並且您希望按順序將函數應用於輸入。

如果功能是純粹的狀態,轉變職能,s -> ss類型的輸入,那你就不要需要State使用它們。 Haskell非常擅長將這些功能鏈接在一起,例如與標準組合運算符.或類似foldr (.) idfoldr id

但是,如果功能都發生變異的狀態報告這樣做,這樣就可以給他們的類型s -> (s,a),然後再刷膠它們放在一起的一些結果是有點討厭的。您必須解壓縮結果元組並將新的狀態值傳遞給下一個函數,在其他位置使用報告的值,然後解壓縮結果,依此類推。將錯誤的狀態傳遞給輸入函數很容易,因爲必須明確命名每個結果並明確輸入才能進行解包。你最終的東西是這樣的:

let 
    (res1, s1) = fun1 s0 
    (res2, s2) = fun2 s1 
    (res3, s3) = fun3 res1 res2 s1 
    ... 
    in resN 

在那裏,我不小心經過s1,而不是s2,也許是因爲我加入了第二條線的後面並沒有意識到的第三行需要改變。當構成s -> s功能,不可能出現這個問題,因爲沒有名字得到的權利:

let 
    resN = fun1 . fun2 . fun3 . -- etc. 

因此,我們發明了State做同樣的伎倆。 State本質上只是一種將s -> (s,a)等函數粘合在一起的方式,使得正確的狀態總是傳遞給正確的函數。

所以它沒有那麼多的人去了「我們要使用State,讓我們使用s -> (s,a)」,而是「我們正在編寫功能,如s -> (s,a),讓我們創造State做出那麼容易」。功能s -> s,它已經很容易,我們不必發明任何東西。

作爲s -> (s,a)自然產生的例子,考慮解析:解析器將被給予一些輸入,消耗一些輸入並返回一個值。在Haskell中,這被自然地建模爲輸入列表,並返回一對值和剩餘的輸入 - 即[Input] -> ([Input], a)State [Input]