2017-02-22 36 views
4

在Haskell中狀態是monad被傳遞來提取和存儲狀態。並且在下面的兩個例子中,兩個都通過使用>>的狀態monad,並且通過函數內聯和縮減來驗證狀態確實傳遞到下一步。混淆了狀態monad在Haskell中的傳遞

然而這似乎不是很直觀。那麼這是否意味着當我想通過狀態單元時,我只需要>>(或>>=和lambda表達式\s -> a,其中sa中不是免費的)?任何人都可以爲這個事實提供一個直觀的解釋,而不用擔心減少這個功能嗎?

-- the first example 
tick :: State Int Int 
tick = get >>= \n -> 
    put (n+1) >> 
    return n 

-- the second example 
type GameValue = Int 
type GameState = (Bool, Int) 

playGame' :: String -> State GameState GameValue 
playGame' []  = get >>= \(on, score) -> return score 
playGame' (x: xs) = get >>= \(on, score) -> 
    case x of 
     'a' | on -> put (on, score+1) 
     'b' | on -> put (on, score-1) 
     'c'  -> put (not on, score) 
     _  -> put (on, score) 
    >> playGame xs 

非常感謝!

回答

5

這真的歸結爲理解狀態是同構的s -> (a, s)。因此,在一次性動作中「包裝」的任何值都是對某種狀態s(產生a的有狀態計算)應用轉換的結果。

傳遞一個狀態的兩個狀態計算

f :: a -> State s b 
g :: b -> State s c 

之間對應於構成它們與>=>

f >=> g 

,或者使用>>=

\a -> f a >>= g 

這裏的結果是

a -> State s c 

它是變換以某種方式一些潛在狀態s有狀態的動作,它被允許訪問某些a和它產生一些c。因此,整個轉換允許取決於a並且值c被允許取決於某個狀態s。這正是您想要表達有狀態計算的內容。整潔的東西(以及將這個機器表達爲monad的唯一目的)就是你不必擔心傳遞狀態。但要了解它是如何完成的,請參考>>=的定義hackage),只是暫時忽略它是變壓器而不是最終monad)。

m >>= k = StateT $ \ s -> do 
    ~(a, s') <- runStateT m s 
    runStateT (k a) s' 

則可以忽略包裝和不包裝使用StateTrunStateT,這裏m在形式上s -> (a, s)k是形式a -> (s -> (b, s))的,並且希望生產狀態轉變s -> (b, s)。所以結果將是s的函數,要生成b您可以使用k但您首先需要a,您如何生成a?您可以採取m並將其應用於狀態s,您將從第一個一元行爲m獲得修改後的狀態s',並將該狀態傳遞給(k a)(其類型爲s -> (b, s))。在這裏,狀態s已經通過m變成s'並且被傳遞給k以成爲最後的s''

對於你作爲這個機制的用戶來說,這仍然是隱藏的,這是關於單子的乾淨事情。如果你想讓一個狀態沿着一些計算進化,你可以用小步驟來構建計算,這些步驟表示爲State -actions,並且你讓do -notation或者綁定(>>=)來進行鏈接/傳遞。

>>=>>之間的唯一區別是您要麼在乎或不關心非狀態結果。

a >> b 

其實相當於

a >>= \_ -> b 

所以什麼都值由動作a獲取輸出,你把它扔掉(僅保留修改狀態),並繼續(沿通狀態)其他動作b


關於你的例子

tick :: State Int Int 
tick = get >>= \n -> 
    put (n+1) >> 
    return n 

您可以在do -notation改寫爲

tick = do 
    n <- get 
    put (n + 1) 
    return n 

一邊寫它的第一種方式使得它也許更加明確什麼是傳遞如何,第二種方式很好地展示了你不必關心它。

  1. 首先get當前狀態(以簡化設置get :: s -> (s, s))揭露它的<-說,你關心的價值和你不想把它扔掉,底層的狀態也在沒有改變的情況下在後臺傳遞(這就是get的工作方式)。

  2. 然後put :: s -> (s -> ((), s)),這是丟棄不必要括號到put :: s -> s -> ((), s)後當量,取值與(第一個參數),以取代目前的狀態,併產生一個狀態動作,其結果是不感興趣的值()您下降(因爲您不使用<-或因爲您使用>>而不是>>=)。由於put基礎狀態已更改爲n + 1,因此它被傳遞。

  3. return 對底層狀態無所作爲,它只有返回其論點。

總之,tick開始與一些初始值s它它更新在內部s+1和輸出s上側。

另一個示例的工作方式完全相同,>>僅用於丟棄由put生成的()。但狀態一直都在傳遞。

+0

謝謝!在閱讀之後,我意識到我只是不明白它的價值被丟棄了,但這個狀態仍然傳播開來。 –

+0

很樂意提供幫助,如果答案是您的滿意,請考慮接受它來解決問題(這適用於所有關於計算器的問題),它有助於跟蹤未解決的問題,並幫助其他人在問自己的問題之前找到答案。 – jakubdaniel

+0

感謝您的提示,太LOL –