Haskell - 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(即,需要首先充分评估函数右操作数),我会认为会发生以下情况:
push 5 >>= (_ -> push x)
pop >>= (x -> s1)
pop >>= (_ -> s2)
这是考虑它的正确方法吗? 我觉得我很好地理解单子,但是我最大的问题在于真正想象“幕后”发生了什么,以及数据如何流动,可以这么说。 该do
记号给出了我处理了一堆顺序操作的,而事实上,有一大堆的嵌套和关闭的错觉。
我感觉有点像我在这里过度思考,并因此导致我自己更加困惑。
从...开始
pop >>= (_ -> pop >>= (x -> push 5 >>= (_ -> push x)))
可以内联一些功能(以更好地显示发生的情况)。 我会从(>>=)
,假装State
没有被定义为一个变换器或newtype,以保持简单。
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'''
这是考虑它的正确方法吗?
没有。
首先:虽然在IO
monad中想到“首先发生这种情况,比这个更好,然后我们评估这个键盘输入......”显然是正确的,但并非所有monad都是如此。 例如,在monad列表中,这没有任何意义。 一般来说,根本不可能在Haskell中计算特定的顺序,它不是定义的行为。
然而,在monad中考虑一个计算顺序总是可能的,而且经常是有帮助的,而这个顺序实际上do
符号建议的顺序。 所以,大多数情况下,思考脱俗的表情并不真切。 但是,如果你想要迈出这一步,以下是我该怎么做:
pop >>= _ -> THUNK1
THUNK1≡> pop >>= x -> THUNK2
{ Closure{x}
} THUNK2≡> push 5 >>= _ -> THUNK3
{ Closure{x}
} THUNK3≡> push x
这当然是荒诞比较难看,但是他说,几乎一样的糖do
表达。
当评估这条线时,绑定实际发生的顺序是什么?
这里没有什么特别的“绑定”。 desugared表达式的评估方式与其他表达式完全相同(惰性),具体取决于您正在使用的特定monad的实现(>>=)
。
如果我们谈论的是使用类似runState
,给予类似的表达foo >>= (x -> bar)
,最外层的表达应用(>>=)
但我们正在努力解开一个newtype
,然后应用里面的函数,所以(>>=)
被强制执行,就像函数一样。
如果我们考虑列表monad,则(>>=)
是concatMap
。 给定一个表达式,如[foo1, foo2] >>= (x -> [bar1, bar2, x] >>= (y -> [baz, y]))
,结果显然不会使用take 5
完全计算所有绑定。
也就是说,这里有一条重要的规则:无论在什么程度上评估x >>= f
强迫x
的评估,在一个像desugared do
块这样的大表达式中,强制将以明显的“顺序”顺序发生,出于同样的原因“顺序错觉”是可能的。