这个State monad代码是如何工作的?

时间:2015-07-19 23:01:53

标签: haskell monads state-monad

此代码来自此article

我已经能够遵循这一部分。

module Test where

type State = Int

data ST a = S (State -> (a, State))

apply        :: ST a -> State -> (a,State)
apply (S f) x = f x

fresh =  S (\n -> (n, n+1))

instance Monad ST where
    -- return :: a -> ST a
    return x   = S (\s -> (x,s))

    -- (>>=)  :: ST a -> (a -> ST b) -> ST b
    st >>= f   = S (\s -> let (x,s') = apply st s in apply (f x) s')

data Tree a = Leaf a | Node (Tree a) (Tree a) deriving (Show)


mlabel  :: Tree a -> ST (Tree (a,Int))
-- THIS IS THE PART I DON'T UNDERSTAND:
mlabel (Leaf x) = do n <- fresh
                     return (Leaf (x,n))
mlabel (Node l r) =  do l' <- mlabel l
                        r' <- mlabel r
                        return (Node l' r')

label t = fst (apply (mlabel t) 0)

tree = Node (Node (Leaf 'a') (Leaf 'b')) (Leaf 'c')

label tree产生:

Node (Node (Leaf ('a',0)) (Leaf ('b',1))) (Leaf ('c',2))

我可以看到>>=运算符是'链接'返回monad(或类似的东西)函数的工具。

虽然我认为我理解这段代码,但我不明白这段代码是如何工作的。

具体来说是do n <- fresh。我们还没有通过任何争论,对吧?在这种情况下n <- fresh会产生什么?绝对不明白。也许它与currying有关?

3 个答案:

答案 0 :(得分:8)

  

特别是n&lt; - 新鲜。我们还没有通过任何争论,对吧?

完全。我们正在编写一个论点,当我们做parse之类的事情时,传递给fresh。一个很好的练习可以帮助您更清楚地了解正在发生的事情,首先使用明确的apply (mlabel someTree) 5而不是do-notation编写mlabel,然后替换(>>=)(>>=)return实例说它们是什么。

答案 1 :(得分:5)

要认识到的关键是do符号被转换为Monad函数,所以

do n <- fresh
   return (Leaf (x,n))

的缩写
fresh >>= (\n -> 
           return (Leaf (x,n))  )

do l' <- mlabel l
   r' <- mlabel r
   return (Node l' r')

的缩写
mlabel l >>= (\l' -> 
              mlabel r >>= (\r' ->
                            return (Node l' r') ))

这有望让您继续查明代码的含义,但是为了获得更多帮助,您应该阅读Monad的do表示法。

答案 2 :(得分:4)

内联的monadic“pipelining”,您的代码变为

fresh state = (state, state + 1)

mlabel (Leaf x) state =                   --  do
  let (n, state') = fresh state           --    n <- fresh
  in  (Leaf (x,n), state')                --    return (Leaf (x,n))

mlabel (Node l r) state =                 -- do
  let (l', state') = mlabel l state       --    l' <- mlabel l
  in let (r', state'') = mlabel r state'  --    r' <- mlabel r
     in  (Node l' r', state'')            --    return (Node l' r') 

main = let (result, state') = mlabel tree 0  
       in  print result                         

{- Or with arrows,

mlabel (Leaf x)   = Leaf . (x ,)  &&&  (+ 1)
mlabel (Node l r) = mlabel l >>> second (mlabel r)
                              >>> (\(a,(b,c)) -> (Node a b,c))
main              = mlabel tree >>> fst >>> print  $ 0
-}

或者在命令式伪代码中:

def state = unassigned

def fresh ():
    tmp = state 
    state := state + 1     -- `fresh` alters the global var `state`
    return tmp             -- each time it is called

def mlabel (Leaf x):       -- a language with pattern matching
    n = fresh ()           -- global `state` is altered!
    return (Leaf (x,n))  

def mlabel (Node l r):
    l' = mlabel l          -- affects the global
    r' = mlabel r          --    assignable variable
    return (Node l' r')    --    `state`

def main:
    state := 0             -- set to 0 before the calculation!
    result = mlabel tree
    print result

计算result更改state可分配的内容;它对应于Haskell的snd元组中的(a, State)字段。元组的fst字段是新构造的树,在其叶子中携带数字和数据。

这些变体在功能上是等效的。

也许你已经听过关于monadic bind是“可编程分号”的口号。这里的含义很明确:它定义了“函数调用协议”,可以这么说,我们使用第一个返回值作为计算结果,第二个返回值作为更新状态,我们将其传递给下一个计算,所以它可以看到更新的状态。

这是状态传递编程风格(对于例如Prolog而言必不可少),使状态更改显式但必须手动负责传递正确的,更新状态。 Monads允许我们抽象出从一个计算到下一个计算的状态“布线”,所以它是为我们自动完成的,代价是必须以命令式的方式思考,并且让这个状态变得隐藏,再次隐含(比如状态变化隐含在命令式编程中,我们想在切换到函数式编程时首先避免这种情况......)。

所以状态monad所做的就是为我们维护这个隐藏状态,并在连续计算之间更新它。所以它毕竟不是 major