[Haskell中涉及IO时如何实现延迟迭代

问题描述 投票:1回答:1

我正在使用IO封装随机性。我正在尝试编写一种方法,该方法将next函数迭代n次,但由于随机性,next函数会产生包装在IO中的结果。

基本上,我的next函数具有此签名:

next :: IO Frame -> IO Frame

并且我想从初始的Frame开始,然后使用与iterate相同的模式来获得长度为[Frame]的列表n。本质上,我希望能够编写以下内容:

runSimulation :: {- parameters -} -> IO [Frame]
runSimulation {- parameters -} = do
  {- some setup -}
  sequence . take n . iterate next $ firstFrame

firstFrame :: IO Frame是通过执行let firstFrame = return Frame x y z之类形成的。

我遇到的问题是,当我运行此函数时,它永远不会退出,因此似乎在无限循环中运行(因为iterate产生了无限列表)。

我对haskell还是很陌生,所以不确定在这里我哪里出错了,或者不确定上面的假设是否正确,似乎整个无限列表都在执行。

haskell io lazy-evaluation
1个回答
0
投票

我不知道每个Frame花费多少时间,但我怀疑您正在做过多的工作。原因有点微妙。 iterate生成功能重复应用的列表。对于列表中的每个元素,将重复使用先前的值。您的列表由IO操作组成。通过应用IO,从位置n-1上已经获得的IO动作中计算位置nnext动作。

A,当executing

这些动作时,我们不是很幸运。在列表中位置n处执行操作将重复先前操作的所有工作!我们在构建动作本身(它们是价值,就像Haskell中的几乎所有内容)时共享工作,但是在执行它们时却没有,这是另一回事。

最简单的解决方案可能是使用烘烤限制来定义此辅助功能:

iterateM :: Monad m => (a -> m a) -> a -> Int -> m [a]
iterateM step = go
    where
    go _ 0 = return []
    go current limit =
        do next <- step current
           (current:) <$> go next (pred limit)

虽然简单,但有点不雅致,有两个原因:

  • 它在限制迭代过程的情况下使迭代过程变得平坦。在纯列表世界中,我们不必这样做,我们可以从那时开始创建无限列表和take。但是现在在有效的世界中,似乎失去了很好的合成性。

  • 如果我们想在生成每个值时都做一些事情,而不必等待所有这些值怎么办? Out函数一次性返回所有内容。


  • 如评论中所述,"conduit""streamly""streaming"之类的流媒体库试图以更好的方式解决此问题,从而重新获得一些纯列表的组合性。这些函数具有表示有效过程的类型,其结果将分段返回。

例如,考虑“流”中的功能Streaming.Prelude.iterateM,专门用于Streaming.Prelude.iterateM

iterateM ::((a-> IO a)-> IO a-> Stream(Of a)IO r] >>

它返回一个IO,我们可以使用Stream“限制”:

take :: Int-> Stream(Of)IO r-> Stream(Of)IO()

限制它之后,我们可以使用Streaming.Prelude.take返回到Streaming.Prelude.take,这会累积所有结果:

toList_ ::流(流)IO r-> IO [a]

但是除此之外,我们还可以使用IO [a]:]这样的功能处理每个元素正在生成时

mapM_ ::((a-> IO x)->流(a)IO r-> IO r

© www.soinside.com 2019 - 2024. All rights reserved.