状态Monad中较新的状态更新为何不会被旧状态覆盖?
状态Monad中状态更新不会被更早状态覆盖的原因分析
你的核心误区是搞反了状态的传递方向——不管是标准State Monad的>>=,还是《Real World Haskell》里的==>,都是前一步输出的新状态作为后一步的输入状态,不存在“更早状态覆盖新状态”的情况,下面一步步拆解:
1. 先看《Learn You a Haskell》的State Monad定义
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
return逻辑简单:把值和原状态打包,不修改状态。>>=的核心逻辑:- 先执行第一个State动作
h,传入初始状态s,得到结果a和更新后的状态newState。 - 把结果
a传给函数f,得到第二个State动作g。 - 最后用**newState(而非初始的s)**去执行
g。
- 先执行第一个State动作
也就是说,后一步的State动作是在前一步更新后的状态基础上运行的,新状态只会被后续步骤继承,不会被更早的初始状态覆盖。
2. 再看《Real World Haskell》的==>逻辑(Monad >>=的雏形)
(==>) :: Parse a -> (a -> Parse b) -> Parse b firstParser ==> secondParser = Parse chainedParser where chainedParser initState = case runParse firstParser initState of Left errMessage -> Left errMessage Right (firstResult, newState) -> runParse (secondParser firstResult) newState
==>的逻辑和>>=完全一致:
- 先运行第一个Parser,传入初始状态
initState,得到结果firstResult和更新后的newState。 - 然后用这个newState去运行第二个Parser(由
firstResult生成的)。
3. 具体拆解parseByte的执行流程
我们一步步走parseByte的运行过程(假设初始状态为initState0):
parseByte = getState ==> \initState -> case L.uncons (string initState) of Nothing -> bail "no more input" Just (byte,remainder) -> putState newState ==> \_ -> identity byte where newState = initState { string = remainder, offset = newOffset } newOffset = offset initState + 1
第一步:
getState ==> ...getState的作用是读取当前状态,所以runParse getState initState0会返回Right (initState0, initState0)(读取到的结果是初始状态,状态本身未修改)。- 接下来把读取到的
initState0传给后面的lambda函数,进入case判断。
第二步:计算newState并执行
putState newState ==> ...- 当有输入时,用读取到的
initState0计算出newState(更新了字符串剩余部分和偏移量)。 - 现在执行
putState newState ==> \_ -> identity byte:- 先运行
putState newState,传入的状态是第一步输出的initState0,runParse (putState newState) initState0会返回Right ((), newState)(putState的作用是把状态设置为newState,结果为())。 - 然后用这个newState去运行后面的
identity byte,runParse (identity byte) newState返回Right (byte, newState)(identity不修改状态,直接返回byte和当前状态)。
- 先运行
- 当有输入时,用读取到的
整个流程结束后,最终的状态是newState——也就是更新后的状态,初始的initState0只是用来生成新状态的中间值,根本不会去覆盖后续的新状态。
本质上,状态是线性传递的:每一步的状态输出都是下一步的输入,新状态只会被后续步骤继承,不存在更早状态回滚覆盖的情况。
内容的提问来源于stack exchange,提问作者planarian
相关产品推荐
相关产品推荐

