簡體   English   中英

Haskell中涉及IO時如何實現lazy iterate

[英]How to implement lazy iterate when IO is involved in Haskell

我正在使用IO來封裝隨機性。 我正在嘗試編寫一個方法來迭代next函數n次,但由於隨機性, next函數產生一個包裝在 IO 中的結果。

基本上,我的next函數具有以下簽名:

next :: IO Frame -> IO Frame

我想從一個初始Frame開始,然后使用與iterate相同的模式來獲取一個長度為n的列表[Frame] 基本上,我希望能夠編寫以下內容:

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

其中firstFrame :: IO Frame通過執行諸如let firstFrame = return Frame xyz

我遇到的問題是,當我運行這個函數時,它永遠不會退出,所以它似乎在無限循環中運行(因為iterate產生一個無限列表)。

我對haskell很陌生,所以不確定我在這里哪里出錯了,或者我上面的假設是否正確,似乎整個無限列表都在執行。


(更新)如果有幫助,以下是FramenextrunSimulation的完整定義:

-- 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操作組成。 位置nIO動作是通過應用next從已經獲得的位置n-1IO動作計算出來的。

唉,在執行這些動作時,我們就沒那么幸運了。 執行列表中位置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”的函數Streaming.Prelude.iterateM ,專門用於IO

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

它返回一個Stream ,我們可以使用Streaming.Prelude.take來“限制”它:

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

在限制它之后,我們可以使用Streaming.Prelude.toList_返回IO [a] ,它會累積所有結果:

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

但是,我們可以在生成每個元素時使用Streaming.Prelude.mapM_函數對其進行處理:

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

一個基本的解決方案:

作為@danidiaz 答案的替代方案,假設可以最小化 IO 的作用,可以在不求助於Streaming等額外庫的情況下解決問題。

大部分需要的代碼都可以用MonadRandom類來寫,IO只是其中的一個實例。 沒有必要使用 IO 來生成偽隨機數。

所需的迭代函數可以這樣寫,用do 表示法

import  System.Random
import  Control.Monad.Random.Lazy

iterateM1 :: MonadRandom mr => (a -> mr a) -> a -> mr [a]
iterateM1 fn x0 = 
    do
        y  <- fn x0
        ys <- iterateM1 fn y
        return (x0:ys)

不幸的是,問題的文本並沒有准確定義 Frame 對象是什么,或者next步進函數是做什么的; 所以我必須以某種方式填補空白。 此外,在所涉及的庫中定義了next名稱,因此我將不得不使用nextFrame而不是next

讓我們假設 Frame 對象只是 3 維空間中的一個點,並且在每個隨機步驟中,隨機選擇 3 維中的一個且僅一個,並且相應的坐標被碰撞了 +1 或-1,概率相等。 這給出了這個代碼:

data Frame = Frame Int Int Int  deriving  Show

nextFrame :: MonadRandom mr => Frame -> mr Frame
nextFrame (Frame x y z) =
    do
        -- 3 dimensions times 2 possible steps: 1 & -1, hence 6 possibilities
        n <- getRandomR (0::Int, 5::Int)
        let fr = case n of
                  0 -> Frame (x-1) y z
                  1 -> Frame (x+1) y z
                  2 -> Frame x (y-1) z
                  3 -> Frame x (y+1) z
                  4 -> Frame x y (z-1)
                  5 -> Frame x y (z+1)
                  _ -> Frame x y z
        return fr

在這一點上,編寫代碼來構建代表模擬歷史的 Frame 對象的無限列表並不困難。 創建該列表不會導致代碼永遠循環,通常的take函數可用於選擇此類列表的前幾個元素。

將所有代碼放在一起:

import  System.Random
import  Control.Monad.Random.Lazy

iterateM1 :: MonadRandom mr => (a -> mr a) -> a -> mr [a]
iterateM1 fn x0 = 
    do
        y  <- fn x0
        ys <- iterateM1 fn y
        return (x0:ys)

data Frame = Frame Int Int Int  deriving  Show

nextFrame :: MonadRandom mr => Frame -> mr Frame
nextFrame (Frame x y z) =
    do
        -- 3 dimensions times 2 possible steps: 1 & -1, hence 6 possibilities
        n <- getRandomR (0::Int, 5::Int)
        let fr = case n of
                  0 -> Frame (x-1) y z
                  1 -> Frame (x+1) y z
                  2 -> Frame x (y-1) z
                  3 -> Frame x (y+1) z
                  4 -> Frame x y (z-1)
                  5 -> Frame x y (z+1)
                  _ -> Frame x y z
        return fr

runSimulation :: MonadRandom mr => Int -> Int -> Int -> mr [Frame]
runSimulation x y z  =  let  fr0 = Frame x y z  in  iterateM1 nextFrame fr0

main = do
    rng0 <- getStdGen  -- PRNG hosted in IO monad
                       -- Could use mkStdGen or MkTFGen instead
    let
         sim  = runSimulation 0 0 0
         allFrames = evalRand sim rng0   -- unlimited list of frames !
         frameCount = 10
         frames = take frameCount allFrames
    mapM_  (putStrLn  .  show)  frames

程序執行:

$ ./frame
Frame 0 0 0
Frame 0 1 0
Frame 0 0 0
Frame 0 (-1) 0
Frame 1 (-1) 0
Frame 1 (-2) 0
Frame 1 (-1) 0
Frame 1 (-1) 1
Frame 1 0 1
Frame 2 0 1
$ 


正如預期的frameCount ,對於frameCount大值,執行時間是frameCount的准線性函數。

更多關於隨機數生成的 monadic 動作在這里

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM