使用 >>= 和 =<< 运算符在 Haskell 中组合 IO

Using the >>= and =<< operators to combine IO in Haskell

我正在尝试运行“无限”模拟,打印每一步的结果。

有一个函数 nextFrameR 接受输入 Map 并将模拟向前推进到 return 输出 Map,然后有一个 render_接受输入 Map 并将一些东西打印到 stdout、return 输入 Map 的函数(以便我可以使用 iterate 或类似的东西) .

作为 Haskell 的新手,我真的很难将所有这些片段组合在一起。我发现 this answer 非常有趣,但由于这两个功能的结合,我不确定如何将其直接付诸实践(我尝试使用 liftM2iterate)。

类型签名如下:

nextFrameR :: Map -> IO Map
render_ :: Map -> IO Map -- originally Map -> IO ()

我不太确定从这里到哪里去,我可以做类似的事情:

(iterate (>>= nextFrameR) initialMap) :: [IO Map]

但这只是给了我一个(无限的?)帧列表(我认为),这很好,它只是不允许我打印它们,因为我不知道如何在其中组合渲染功能.

iterate 对于非 IO 计算工作得很好,但是如果你在 IO 中,你不能轻易利用 iterate.

看看为什么,你的列表

(iterate (>>= nextFrameR) initialMap) :: [IO Map]

[ initialMap
, initialMap >>= nextFrameR
, initialMap >>= nextFrameR >>= nextFrameR
...

所以...我们如何利用它来实现无限循环?我们不能拿不存在的“最后一个元素”。我们也不能按顺序执行该列表中的所有操作,因为那样会 运行 initialMap 很多次。

如果我们避免使用 iterate 并求助于递归等基础知识会更容易:

loop :: Map -> IO ()
loop m = do
   m' <- nextFrameR m
   render_ m'       -- it looks like you want this
   -- feel free to add some delay here, or some stopping condition to exit the loop
   loop m'

main :: IO ()
main = do
   m <- initialMap
   loop m

你可以把上面的代码变成一些使用>>=的代码,但没有必要。

最后,没必要让render_return一样Map。你可以做到 return IO ().

如果您是初学者,我建议您最初远离 mapM_, traverse, for, sequence, liftM2, ap, ... 等“智能”库函数,并学习仅使用 do 块和递归来完成所有操作。然后,一旦您了解了它的工作原理,您就可以尝试利用库助手改进您的代码。