我正在编写一个包含RWS
的程序,用于跟踪可变状态并生成一些日志。我的目的是定义一个计算,该计算对某些操作进行评估,收集后继状态,并根据此状态将一些内容附加到Writer
日志的开头。最小示例:
type M = RWS () String [Int]
run a = evalRWS a () []
prepend s = tell (foldMap show s)
act = do
tell "a"
modify (1:)
tell "b"
comp = mfix $ \s -> prepend s >> act >> get
在这里,我使用
MonadFix
通过在act
发生之前写日志来更改过去。它可以完美地返回"1ab"
。但是,如果我使用M
遍历状态,则会挂起:prepend s = forM_ s (tell . show)
这种行为对我来说很奇怪,我不明白为什么这种计算会有所不同。由于第二个变体中的
prepend
不会在任何程度上改变状态,因此更难辩解。为什么该程序不收敛?我有什么可以解决的(inb4“hehe fix”)吗?我知道我可以使用
State
的RWS
部分解决该问题,但是由于某些原因,我想避免它。 最佳答案
发生这种情况是因为forM_
不是惰性的。在the mfix
documentation中明确调用了此要求:该函数必须是惰性的,以便收敛定点。但是forM_
确实需要解构其参数以便对其进行迭代。对于列表中的每个元素,它仍然可以保持懒惰,但对于列表本身而言却可以。
当您运行这种递归式计算时,它需要三个步骤(即三个monadic绑定(bind)):prepend
,然后act
,然后是get
,因此,您实际上得到的值看起来像这样:
[foldMap show s, "a", "b"]
尚未评估
foldMap show s
片段的地方-即它是一个笨拙的指向s
,这是同一计算的最终状态。因为Haskell是惰性的,所以可以甚至在评估状态之前引用该状态以将其合并到foldMap show s
表达式中。这是工作的懒惰。但是,如果将
prepend
替换为foldM_
,则您的计算将不再包含三个步骤(三个Monadic绑定(bind))。现在,对于结果状态列表的每个元素,您都有一个步骤。这意味着要构建计算(即定义其步骤,也称为绑定(bind)),您需要检查其自身的结果状态。