【问题标题】:Using the >>= and =<< operators to combine IO in Haskell使用 >>= 和 =<< 运算符在 Haskell 中组合 IO
【发布时间】:2021-04-04 13:01:01
【问题描述】:

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

有一个函数 nextFrameR 接受输入 Map 并向前模拟以返回输出 Map,然后有一个 render_ 函数接受输入 Map 并将一些内容打印到 @ 987654328@,返回输入Map(这样我就可以使用iterate 或类似的东西)。

我真的很难将所有这些部分放在一起,因为我对 Haskell 来说还比较陌生。我发现this answer 非常有趣,但由于这两个功能的结合,我不确定如何直接将其付诸实践(我尝试过使用liftM2iterate)。

类型签名如下:

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

我不确定从这里去哪里,我可以这样做:

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

但这只是给了我一个(无限?)帧列表(我认为),这很好,它只是不让我打印它们,因为我不知道如何在那里组合渲染功能。

【问题讨论】:

  • 表达式 sequence (iterate (&gt;&gt;= nextFrameR) initialMap) 将具有 IO [Map] 类型。但是,与其在早期阶段涉及 IO,您可以使用:frames = iterate nextFrameR initialMap,编写一个 Map -&gt; String 类型的普通渲染函数。然后使用map render frames 查看文本跟踪输出。恕我直言,是否使用 IO 的决定应该留给顶层调用代码。
  • 问题是nextFrameR 在每次运行时都会创建一个newStdGen。我有一个代码版本,它采用 RandomGen(这是 nextFrameR 在后台调用的),但我不在顶层。
  • 我明白了,但是在每一步都创建一个新的生成器有点不寻常。您可以将步骤 N 中随机数生成器的最终状态传递为步骤 N+1 中生成器的初始状态。这样,您将获得统计保证,这些保证仅在单个随机系列中可用。如果您希望能够使用相同的随机数但不同的物理参数运行以后的模拟,那么 newStdGen 使这成为不可能,因为您无法控制种子。
  • @jpmarinier 这就是为什么我有一个使用RandomGen 代替的函数版本(github.com/gfarrell/tron-lines/blob/…)。我认为这解决了您正在谈论的问题,但也许没有?我确实希望能够获得可重复的测试结果。
  • 不完全是。我在您的 Github 页面中看到您的函数具有这种类型签名:nextFrame :: RandomGen gen =&gt; gen -&gt; Map -&gt; Map。因此它不会保留生成器的最终状态以供进一步处理。你可能想要这个签名:nextFrame :: RandomGen gen =&gt; gen -&gt; Map -&gt; (Map, gen)。但我看到 library shuffle' 函数 drops 最终状态。请参阅其源代码中对fst 的调用,而不是对snd 的调用。因此,您将不得不调整 shuffle' 源代码或安排将 shuffleMrunRand 一起使用。

标签: list haskell monads lazy-evaluation lazy-sequences


【解决方案1】:

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

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

最后,没有必要让render_ 返回相同的Map。你可以让它返回IO ()

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

【讨论】:

  • 我解决这个问题的本能是使用 do 块来解决它,但是使用它编写了一些代码然后重构为使用 >>= 等我试图做同样的事情。你说得对,我可能应该先尝试更简单的版本,我现在将尝试看看它是否有效。
  • 这行得通,但根据最初的问题,是否有一种好方法可以使用各种运算符将代码转换为“更智能”的东西?感觉这就是这些 monad 和各种运算符的用途。
  • @GTF 这是一个品味问题。简单的do 代码很短,任何人都可以阅读。我不能立即看到如何仅使用 常用 助手来实现这一点。当然有很多不常用的助手,例如iterateM 似乎完全符合您的需求。这应该仍然没问题,但是当您使用高级助手时,请始终检查代码是否仍然可读。当代码不可读时,编写“聪明”的代码是没有意义的。
  • "base" 不包括适当的流式 API 可能会让新手感到困惑。人们学习了那些在纯上下文中完美运行的漂亮列表处理功能,但是一旦在某处添加效果,就会发现mapM 和朋友不会按需要“流式传输”。然后将其简化为编写更多“整体”的单子块,这些块在一定程度上将生成和处理结合起来,给人的印象是那些更高级别的功能是为纯世界保留的。
  • @GTF 如果你真的想使用现有的运算符,你可以写loop = fix ((render_ &gt;=&gt; nextFrameR) &gt;=&gt;)。根据您的审美,还有各种其他拼写,包括loop = fix (\f -&gt; render_ &gt;=&gt; nextFrameR &gt;=&gt; f)loop = fix (\f n -&gt; render_ n &gt;&gt;= nextFrameR &gt;&gt;= f)loop = render_ &gt;=&gt; nextFrameR &gt;=&gt; loop
猜你喜欢
  • 2022-07-13
  • 2021-12-25
  • 2013-04-01
  • 2010-11-30
  • 2013-12-06
  • 1970-01-01
  • 2013-02-27
  • 1970-01-01
  • 2011-10-01
相关资源
最近更新 更多