我在搞清楚/如何/绑定运算符实际绑定以下状态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 回答
从...开始
可以内联一些函数(以显示更好的情况) . 我将从
(>>=)
开始,假装State
未定义为变换器或新类型,以保持简单 .没有 .
首先:虽然在
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
表达几乎相同 ."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
,在一个像desberareddo
块这样的大表达式中,强制将以明显的"sequential"顺序发生,原因与"sequential illusion"是可能的相同 .