[英]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很陌生,所以不確定我在這里哪里出錯了,或者我上面的假設是否正確,似乎整個無限列表都在執行。
(更新)如果有幫助,以下是Frame
、 next
和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
操作組成。 位置n的IO
動作是通過應用next
從已經獲得的位置n-1的IO
動作計算出來的。
唉,在執行這些動作時,我們就沒那么幸運了。 執行列表中位置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.