Haskell - Monad绑定评估顺序

时间:2013-04-10 12:52:16

标签: haskell monads

我在搞清楚/如何/绑定运算符实际绑定以下状态monad时遇到了一些麻烦:

pop :: State [Int] Int
pop = do
        (x:xs) <- get
        put xs
        return x

push :: Int -> State [Int] ()
push x = do 
            xs <- get
            put (x:xs)

doStuff :: State [Int] ()
doStuff = do
            pop
            x <- pop
            push 5
            push x

doStuff,可以去掉以下内容:

pop >>= (\_ -> pop >>= (\x -> push 5 >>= (\_ -> push x)))

评估此行时,绑定实际发生的顺序是什么?因为,为了实际绑定,Haskell需要从>>=运算符右侧的函数中获取状态monad(即函数右操作数需要首先被完全评估),我会认为以下会发生:

  1. s1 = push 5 >>= (\_ -> push x)
  2. s2 = pop >>= (\x -> s1)
  3. s3 = pop >>= (\_ -> s2)
  4. 这是考虑它的正确方法吗?我觉得我很了解monad,但我最大的问题在于实际可视化“幕后”发生的事情以及数据如何流动,可以这么说。 do符号表示我正在处理一系列顺序操作的错觉,实际上,有一大堆嵌套和闭包。

    我觉得我有点过于思考这里的事情,结果让自己更加困惑。

3 个答案:

答案 0 :(得分:8)

开始
pop >>= (\_ -> pop >>= (\x -> push 5 >>= (\_ -> push x)))

可以内联一些函数(以显示更好的情况)。我将从(>>=)开始,假装State未定义为变换器或新类型,以保持简单。

type State s a = s -> (a, s)
m >>= k = \ s -> let (a, s') = m s in k a s'

\ s -> let (a, s') = pop s in
(\ _ -> pop >>= (\ x -> push 5 >>= (\ _ -> push x))) a s'

\ s -> let (_, s') = pop s in
(pop >>= (\ x -> push 5 >>= (\ _ -> push x))) s'

\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
(\ x -> push 5 >>= (\ _ -> push x)) a s''

\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
(push 5 >>= (\ _ -> push a)) s''

\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
let (b, s''') = push 5 s'' in
(\ _ -> push a)) b s'''


\ s -> let (_, s') = pop s in
let (a, s'') = pop s' in
let (_, s''') = push 5 s'' in
push a s'''

答案 1 :(得分:3)

  

这是考虑它的正确方法吗?

没有

首先:虽然在IO monad中考虑“首先发生这种情况,然后我们评估这个键盘输入...”显然是正确的,但对于所有monad来说都不是这样。 。例如,在monad列表中,这没有任何意义。通常,根本不可能在Haskell中为计算分配特定的顺序,它不是定义的行为。

然而,考虑monad中的计算顺序总是可能,并且经常是有用的,而且这个顺序实际上是do表示法建议的顺序。所以,大多数时候,考虑一下这种贬低的表达并不是很有见地。但如果你想做出这一步,我就是这样做的:

  1. pop >>= \_ -> THUNK1

  2. THUNK1≡&gt; pop >>= \x -> THUNK2

  3. {Closure{x}}THUNK2≡&gt; push 5 >>= \_ -> THUNK3

  4. {Closure{x}}THUNK3≡&gt; push x

  5. 这当然更加丑陋,但与含糖的do表达几乎相同。

答案 2 :(得分:2)

  

评估此行时,绑定实际发生的顺序是什么?

这里的“绑定”没有什么特别之处。 desugared表达式的评估方式与其他表达式完全相同(懒惰),具体取决于(>>=)对您正在使用的特定monad的实现。

如果我们谈论使用类似runState的内容,给定类似foo >>= (\x -> bar)的表达式,最外面的表达式是(>>=)的应用程序,但我们正在尝试打开newtype 1}}然后在内部应用函数,因此(>>=)被强制,函数也是如此。

如果我们考虑列表monad,(>>=)concatMap。给定[foo1, foo2] >>= (\x -> [bar1, bar2, x] >>= (\y -> [baz, y]))之类的表达式,在结果上使用take 5显然无法完全计算所有绑定。


也就是说,这里有一个重要的规则:无论评估x >>= f如何强制评估x,在一个像des do块这样的大表达式中,强迫将发生在明显的“顺序”顺​​序,原因与“连续错觉”是可能的相同。