我正在使用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还是很陌生,所以不确定在这里我哪里出错了,或者不确定上面的假设是否正确,似乎整个无限列表都在执行。
我不知道每个Frame
花费多少时间,但我怀疑您正在做过多的工作。原因有点微妙。 iterate
生成功能重复应用的列表。对于列表中的每个元素,将重复使用先前的值。您的列表由IO
操作组成。通过应用IO
,从位置n-1上已经获得的IO
动作中计算位置n的next
动作。
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