我在搞清楚/如何/绑定运算符实际绑定以下状态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)
这是考虑它的正确方法吗?我觉得我很了解monad,但我最大的问题在于实际可视化“幕后”发生的事情以及数据如何流动,可以这么说。 do
符号表示我正在处理一系列顺序操作的错觉,实际上,有一大堆嵌套和闭包。
我觉得我有点过于思考这里的事情,结果让自己更加困惑。
答案 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
表示法建议的顺序。所以,大多数时候,考虑一下这种贬低的表达并不是很有见地。但如果你想做出这一步,我就是这样做的:
pop >>= \_ -> THUNK1
THUNK1≡&gt; pop >>= \x -> THUNK2
{Closure{x}
}THUNK2≡&gt; push 5 >>= \_ -> THUNK3
{Closure{x}
}THUNK3≡&gt; push x
这当然更加丑陋,但与含糖的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
块这样的大表达式中,强迫将发生在明显的“顺序”顺序,原因与“连续错觉”是可能的相同。