【发布时间】:2020-06-14 11:10:32
【问题描述】:
考虑以下代码段
import Control.Monad.State
type Stack = [Int]
pop :: State Stack Int
pop = state $ \(x:xs) -> (x, xs)
push :: Int -> State Stack ()
push a = state $ \xs -> ((), a:xs)
stackManip :: State Stack Int
stackManip = do
push 3
a <- pop
pop
众所周知,Monad 中的 do notation 与 >>= 运算符相同。我们可以将此段改写为:
push 3 >>= (\_ ->
pop >>= (\a ->
pop))
这个表达式的最终值与push 3和第一个pop无关,无论输入什么,它都只是返回pop,所以不会先将值3入栈并弹出它,为什么会发生这种情况?
感谢您的回复。我在上面添加了缺少的代码(Stack、push 和pop 的实现),我想我已经弄清楚它是如何工作的。理解这段代码的关键是理解State smonad的实现:
instance Monad (State s) where
return x = State $ \s -> (x, s)
(State h) >>= f = State $ \s -> let (a, newState) = h s
(State g) = f a
in g newState
>>=do 的整个实现是将状态传递给函数h,计算其新状态并将新状态传递给函数g 隐含在 f 中,获得更新的状态。 所以实际上状态已经隐式改变了。
【问题讨论】:
-
没有时间详细回答,但这主要是因为 State monad 的
>>=是如何定义的。它确保当状态被一个表达式改变时,更新的值被传递给下一个。 -
Stack、push和pop是如何定义的? -
“不会”是什么意思?当然会的,你在代码中写了很多。您首先推送 3,然后将其弹出(否定推送,是的,但编译器为什么要关心?在任何情况下它都是不可观察的(除非通过检查编译器生成的代码)),然后再执行一次弹出,返回其值(大概这就是
pop的定义方式)。所以等效的 sn-p 是push 3 >>= (\() -> pop >>= (\3 -> pop))。 (可能,如果这就是push的定义方式)。你的问题到底是什么? Haskell 是 YAPL,程序员负责。 -
Statemonad 的全部意义在于隐藏状态的显式传递(在这种情况下,是被操作的堆栈)。 -
@chepner 我认为你搞错了。它确实从 second pop AFAICS 返回值。它忽略了第一次弹出的值(这与它在第一次弹出之前刚刚推送的 3 相同)。
标签: haskell functional-programming monads state-monad do-notation