2

我正在编写一个程序,该程序涉及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”)吗?

我知道我可以使用Statepart of解决它RWS,但出于某种原因,我想避免它。

4

2 回答 2

3

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

您的第一个版本可以正常工作,因为它不需要检查状态即可将其配对。

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

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

prepend s = tell (concatMap show s)
于 2020-01-03T13:51:36.827 回答
3

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


当你运行你的这个递归式计算时,它需要三个步骤(即三个单子绑定):prepend、 thenact和 then get,结果你基本上得到一个看起来像这样的值:

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

foldMap show s尚未评估该部分的地方- 即它是指向 的 thunk s,这是同一计算的最终状态。甚至可以在评估状态之前引用状态以将其合并到foldMap show s表达式中,因为 Haskell 是惰性的。这是工作上的懒惰。

但是,如果替换prependfoldM_,则计算中不再有三个步骤(三个单子绑定)。现在,您对结果状态列表的每个元素都有一个步骤。这意味着,为了构建计算(即定义它的步骤,也就是绑定),您需要检查它自己的结果状态。

于 2020-01-03T14:09:04.033 回答