Haskell:如果遍历参数,RWS 上的一元不动点是循环的



我正在编写一个程序,该程序涉及用于跟踪可变状态并生成一些日志的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"嘿嘿修复"(它?

我知道我可以使用StateRWS的一部分来解决它,但出于某种原因,我想避免它。

发生这种情况是因为forM_并不惰。此要求在mfix文档中明确指出:函数必须是延迟的,以便固定点收敛。但是forM_确实需要解构其参数才能对其进行迭代。它仍然可以对列表的每个元素保持懒惰,但不能保持列表本身。


当你运行这个递归式计算时,它需要三个步骤(即三个一元绑定(:prepend,然后是act,然后是get,结果你基本上得到一个看起来像这样的值:

[foldMap show s, "a", "b"]

其中foldMap show s块尚未被评估 - 即它是一个指向s的投掷,这是相同计算的最终状态。在计算状态之前,可以引用状态以将其合并到foldMap show s表达式中,因为 Haskell 很懒惰。这是工作上的懒惰。

但是,如果将prepend替换为foldM_,则计算中不再有三个步骤(三个一元绑定(。现在,对于生成的状态列表的每个元素,您都有一个步骤。这意味着为了构建计算(即定义其步骤,也称为绑定(,您需要检查其自己的结果状态。

只有在定义了s时才定义forM_ s u,但这里s是一个由mfix传递的占位符,它只会在整个计算prepend s >> act >> get终止后定义。

您的第一个版本之所以有效,是因为它不需要检查状态即可将其放入一对中。

mfix :: (a -> m a) -> m a不接受严格的函数f :: a -> m a(即,这样f undefined = undefined(。

如果你有一个要tell的事情列表,那么一个更懒惰的方法是在告诉它们之前将它们连接起来:

prepend s = tell (concatMap show s)

最新更新