当IO参与Haskell时如何实现延迟迭代 [英] How to implement lazy iterate when IO is involved in Haskell

查看:102
本文介绍了当IO参与Haskell时如何实现延迟迭代的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using IO to encapsulate randomness. I am trying to write a method which iterates a next function n times, but the next function produces a result wrapped in IO because of the randomness.

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

Basically, my next function has this signature:

next :: IO Frame -> IO Frame

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

and I want to start with an initial Frame, then use the same pattern as iterate to get a list [Frame] with length n. Essentially, I'd like to be able to write the following:

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之类的方式形成的.

Where firstFrame :: IO Frame formed by doing something like let firstFrame = return Frame x y z.

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

The problem I am encountering is that when I run this function, it never exits, so it seems to be running on an infinite loop (since iterate produces an infinite list).

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

I'm quite new to haskell so not sure where I'm going wrong here, or if my supposition above is correct that it seems that the entire infinite list is being executed.

(更新)如果有帮助,请参见FramenextrunSimulation的完整定义:

(Update) In case it's helpful, here are the full definitions of Frame, next, and runSimulation:

-- A simulation Frame encapsulates the state of the simulation at some
-- point in "time". That means it contains a list of Agents in that
-- Frame, and a list of the Interactions that occurred in it as well. It
-- also contains the state of the World, as well as an AgentID counter
-- (so we can easily increment for generating new Agents).
data Frame = Frame AgentID [Agent] [Interaction]
  deriving Show

-- Generate the next Frame from the current one, including scoring the
-- Agents based on the outcomes *in this Frame*.
-- TODO: add in reproduction.
nextFrame :: Reactor -> World -> IO Frame -> IO Frame
nextFrame react w inp = do
  (Frame i agents history) <- inp
  interactions <- interactAll react history agents
  let scoredAgents = scoreAgents (rewards w) interactions agents
  return (Frame i scoredAgents interactions)

-- Run a simulation for a number of iterations
runSimulation :: World -> Reactor -> (Dist, Dist) -> IO [Frame]
runSimulation world react (gen_dist, sel_dist) = do
  startingAgents <- spawnAgents (initial_size world) (agentCreatorFactory gen_dist sel_dist)
  let firstFrame = return (Frame (length startingAgents) startingAgents [])
      next       = nextFrame react world
  sequence . take (iterations world) . iterate next $ firstFrame

推荐答案

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

I don't know how much time computing each Frame takes, but I suspect you are doing more work than necessary. The cause is a bit subtle. iterate produces a list of repeated applications of a function. For each element in the list, the previous value is reused. Your list is composed of IO actions. The IO action at position n is computed from the already obtained IO action at position n-1 by applying next.

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

Alas, when executing those actions, we are not so lucky. Executing the action at position n in the list will repeat all the work of the previous actions! We shared work when building the actions themselves (which are values, like almost everything in Haskell) but not when executing them, which is a different thing.

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

The simplest solution could be to define this auxiliary function with a baked-in limit:

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)

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

While simple, it's a bit inelegant, for two reasons:

  • 它在限制迭代过程的情况下使迭代过程更加紧凑.在纯列表世界中,我们不必这样做,我们可以从那时开始创建无限列表和take.但是现在在有效的世界中,似乎失去了很好的构图性.

  • It conflates the iteration process with the limiting of such process. In the pure list world we didn't have to do that, we could create infinite lists and take from then. But now in the effectful world that nice compositionality seems to be lost.

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

What if we want to do something with each value as it is being produced, without having to wait for all of them? Out function returns everything at the end, in one go.

如评论中所述,流式库如导管" "streamly" 流式处理" 试图以更好的方式解决此问题,重新获得一些纯列表的组合性.这些库具有代表有效过程的类型,其结果是分段产生的.

As mentioned in the comments, streaming libraries like "conduit", "streamly" or "streaming" try to solve this problem in a better way, regaining some of the compositionality of pure lists. These libraries have types that represent effectful processes whose results are yielded piecewise.

例如,考虑函数 Streaming.Prelude.iterateM 从流"开始,专门用于IO:

iterateM ::(a-> IO a)-> IO a->流(流)IO r

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

它返回一个Stream,我们可以使用

It returns a Stream that we can "limit" using Streaming.Prelude.take:

take :: Int->流(一个)IO r->流(一个)IO()

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

限制后,我们可以使用IO [a] rel ="nofollow noreferrer"> Streaming.Prelude.toList_ 会累积所有结果:

after limiting it we can get back to IO [a] with Streaming.Prelude.toList_ which accumulates all results:

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

toList_ :: Stream (Of a) IO r -> IO [a]

但除此之外,我们还可以处理每个元素的生成过程,其功能类似于

But instead of that we could process each element as it is being produced, with functions like Streaming.Prelude.mapM_:

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

mapM_ :: (a -> IO x) -> Stream (Of a) IO r -> IO r

这篇关于当IO参与Haskell时如何实现延迟迭代的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆