【问题标题】:How does this State monad code works?这个 State monad 代码是如何工作的?
【发布时间】:2015-07-19 23:01:52
【问题描述】:

此代码来自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))

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

虽然我认为我理解这段代码,但我不明白这段特定代码的工作原理。

特别是do n &lt;- fresh。我们还没有向fresh传递任何论据,对吧?在这种情况下,n &lt;- fresh 会产生什么?完全不明白这一点。也许它与咖喱有关?

【问题讨论】:

    标签: haskell monads state-monad


    【解决方案1】:

    专门做n

    没错。例如,当我们执行apply (mlabel someTree) 5 之类的操作时,我们正在编写一个 传递给fresh 的论点。一个很好的练习可以帮助您更清楚地看到正在发生的事情,首先使用显式 (&gt;&gt;=) 而不是 do-notation 编写 mlabel,然后将 (&gt;&gt;=)return 替换为 Monad 实例所说的内容他们是。

    【讨论】:

    • "will" 我认为绝对是理解单子的关键词。
    【解决方案2】:

    要实现的关键是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') ))
    

    这有望让您继续弄清代码的含义,但要获得更多帮助,您应该阅读 Monads 的 do 表示法。

    【讨论】:

    • 我可以分解freshmlabelreturn 在这里所做的事情,但我认为您将通过自己弄清楚这一点来了解更多关于 ST monad 的信息。 (即我不想过早地过度解释。)
    • 'mlabel l' 为我们提供了将 State 作为输入并产生 (Leaf (x, oldState), NewState) 的状态转换器,对吗?如果是这样,那么在那之后我们如何使用 bind for 'mlabel r' 呢?我们在 ''mlabel l' 之后绑定的函数不应该具有 Leaf (Char, State) -> ST b 类型吗?
    • @user1685095 因为 ST 是 Monad 的一个实例:在 (&gt;&gt;=) :: Monad m =&gt; m a -&gt; (a -&gt; m b) -&gt; m b 中,m 可以替换为 ST。所以ST a 可以在&gt;= 的左侧。这也是mlabel r 的类型。
    • @user1685095 Leaf (Char, State) -&gt; ST b 不是类型,因为 Leafdata 构造函数。 mlabel (l :: Tree a) :: ST (Tree (a,Int)) ~~ State -&gt; ( Tree (a,Int), State)l' 具有 Tree (a,Int) 类型,但 l 具有 Tree a 类型,r 也是如此。请注意,第二个绑定嵌套在第一个绑定的目标函数中。 l' 中的值被搁置一旁,并使用第二个目标函数执行第二次绑定,在范围内仍然可以访问 l',因此 return 可以同时使用 @987654348 @ 和 l'.
    【解决方案3】:

    通过内联单子“流水线”,您的代码变为

    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 的 (a, State) 元组中的 snd 字段。元组的fst 字段是新构建的树,在其叶子中的数据旁边带有编号。

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

    也许您听说过关于单子绑定是“可编程分号”的流行语。这里意思就很明确了:可以说是定义了“函数调用协议”,我们将第一个返回值作为计算结果,第二个返回值作为更新状态,传递给下一次计算,所以它可以看到 updated 状态。

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

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

    【讨论】:

    • 我的理解是否正确 mlabel l >>= (\l' -> mlabel r >>= (\r' -> return (Node l' r') )) 提供了咖喱函数?这段代码可以像你写的那样写吗? mlabel l, state >>= \l' ... 等等?
    • 我理解 monad 本质上是做什么的。我想我只是不太习惯阅读用柯里化函数编写的代码。你写的函数式代码基本上是没有柯里化的代码,对吧?
    • mlabel l &gt;&gt;= f == S (\s -&gt; let (x,s') = apply (mlabel l) s in apply (f x) s')。所以它是一个参数s 的函数。我不知道你所说的“咖喱”是什么意思。这是一个功能。在 Haskell 中,g x y = ...g x = \y -&gt; ... 相同。两者都是定义。这些方程式不会在遇到新对象时在内存中“创建”新对象——整个程序都会被编译。并且由于懒惰,我们可以通过等式推理来分析/转换程序。并且允许编译器这样做。
    • 没有mlabel l, state &gt;&gt;= \l';相反,S 下的函数被应用到状态apply (S f) s = f s,所以由于mlabel l &gt;&gt;= f = S \s-&gt; ...,最终将\s-&gt; ... 应用到状态参数。 Haskell 的函数是柯里化的,f x y = ... 意味着 f x 是一个函数,它可能稍后应用于y。因此mlabel l 定义的函数将在某个时候应用于某个初始状态。在您的程序中,label t = ... 会这样做。
    • 你真的应该试着把它全部写出来,就像 AndrewC 建议的那样,耐心地,一步一步。如果您对此有任何疑问,请联系我,我会将其添加到答案中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-17
    • 2010-12-13
    • 2013-04-23
    • 2015-04-09
    • 2013-07-24
    • 2014-03-26
    • 2012-11-14
    相关资源
    最近更新 更多