首页 文章

Haskell - Monad绑定评估顺序

提问于
浏览
3

我在搞清楚/如何/绑定运算符实际绑定以下状态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(即函数右操作数需要首先被完全评估),我会认为会发生以下情况:

  • s1 = push 5 >>= (\_ -> push x)

  • s2 = pop >>= (\x -> s1)

  • s3 = pop >>= (\_ -> s2)

这是考虑它的正确方法吗?我觉得我很了解monad,但我最大的问题在于实际可视化正在发生的事情以及数据如何流动,可以这么说 . do 符号给出了一个错觉,即我是一堆嵌套和闭包 .

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

3 回答

  • 2

    从...开始

    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'''
    
  • 8

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

    没有 .

    首先:虽然在 IO monad中想到"first this happens, than that, then we evaluate this keyboard input..."显然是正确的,但这真的没有任何意义 . 一般来说,它没有定义行为 .

    然而,考虑monad中的计算顺序总是可能的,而且通常很有帮助,而且这个顺序实际上是 do 表示法所建议的顺序 . 所以,大部分时间我都不知道如何做到这一点:

    • pop >>= \_ -> THUNK1

    • THUNK1≡> pop >>= \x -> THUNK2

    • { Closure{x} }THUNK2≡> push 5 >>= \_ -> THUNK3

    • { Closure{x} }THUNK3≡> push x

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

  • 3

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

    "binding"这里没什么特别的 . desugared表达式的评估方式与其他任何表达式完全相同(懒惰),具体取决于 (>>=) 对于您正在使用的特定monad的实现 .

    如果我们正在谈论使用 runState 之类的东西,给定像 foo >>= (\x -> bar) 这样的表达式,最外面的表达式是 (>>=) 的应用程序,但是我们试图打开 newtype 然后在内部应用函数,所以_3042710被强制,就像功能 .

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


    也就是说,这里有一个重要的规则:无论在什么程度上评估 x >>= f 强制评估 x ,在一个像desberared do 块这样的大表达式中,强制将以明显的"sequential"顺序发生,原因与"sequential illusion"是可能的相同 .

相关问题