2016-09-09 38 views
10

我有一個Producer創建依賴於隨機性的值,用我自己的Random單子:我如何通過IO操作在一些非IO monad中慣用和高效地使用管道?

policies :: Producer (Policy s a) Random x 

Random超過mwc-random的包裝,可以從STIO運行:

newtype Random a = 
    Random (forall m. PrimMonad m => Gen (PrimState m) -> m a) 

runIO :: Random a -> IO a 
runIO (Random r) = MWC.withSystemRandom (r @ IO) 

policies生產者通過簡單的強化學習算法產生更好更好的策略。

我可以有效地繪製後的政策,比方說,500萬次迭代通過索引進入policies

Just convergedPolicy <- Random.runIO $ Pipes.index 5000000 policies 
plotPolicy convergedPolicy "policy.svg" 

我現在要繪製每50萬步中間政策,看看他們是如何收斂。我寫了幾個函數,生成policies製作者,並提取一個列表([Policy s a]),比如10個策略 - 每500,000次迭代一次 - 然後繪製所有這些策略。但是,即使學習迭代的總數應該相同(即5,000,000),這些函數花費的時間(10x)和使用更多內存(4x)的時間也要比繪製上述最終策略要多。我懷疑這是由於抽取名單抑制垃圾收集器,這似乎是一個unidiomatic使用管道的:因爲它們產生的,而不是加載的所有元素到內存

慣用管道風格立即消耗的元素。

什麼是正確的做法,以消耗管這樣當Producer是在一些隨機的單子(即Random)和IO我想產生的效果是什麼?

換句話說,我想插入一個Producer (Policy s a) Random xConsumer (Policy s a) IO x

+0

'Random'是否有'MonadIO'實例?如果是這樣的話,你將會有'hoist liftIO :: Consumer(Policy s a)IO x - > Consumer(Policy s a)Random x'。還有其他的方法可以解決這個問題,有些可能會更快,但它可能有助於更多地瞭解「Random」。 – Michael

+1

@邁克爾:它不,我不希望它。它只能做隨機性和*不*通用IO。理想情況下,我很喜歡這個解決方案,它只依賴於'Random'具有'runIO :: Random a - > IO a'功能的事實。 –

+1

我想你不希望由於生成隨機性的方式而使'hoist runIO'將'Producer(Policy s a)Random x'轉換爲'Producer(Policy s a)IO x'。 – Michael

回答

2

Random是一個閱讀器,讀取發電機

import Control.Monad.Primitive 
import System.Random.MWC 

newtype Random a = Random { 
    runRandom :: forall m. PrimMonad m => Gen (PrimState m) -> m a 
} 

我們可以平凡轉換Random aReaderT (Gen (PrimState m)) m a。這瑣碎操作是您想要hoistProducer ... Random a變成Producer ... IO a的操作。

import Control.Monad.Trans.Reader 

toReader :: PrimMonad m => Random a -> ReaderT (Gen (PrimState m)) m a 
toReader = ReaderT . runRandom 

由於toReader是微不足道不會有從hoist任何隨機生成的開銷荷蘭國際集團它。這個函數是爲了演示它的類型簽名而編寫的。

import Pipes 

hoistToReader :: PrimMonad m => Proxy a a' b b' Random       r -> 
           Proxy a a' b b' (ReaderT (Gen (PrimState m)) m) r 
hoistToReader = hoist toReader 

這裏有兩種方法。簡單的方法是將hoistConsumer放入同一個monad中,將管道組合在一起,然後運行它們。

type ReadGenIO = ReaderT GenIO IO 

toReadGenIO :: MFunctor t => t Random a -> t ReadGenIO a 
toReadGenIO = hoist toReader 

int :: Random Int 
int = Random uniform 

ints :: Producer Int Random x 
ints = forever $ do 
    i <- lift int 
    yield i 

sample :: Show a => Int -> Consumer a IO() 
sample 0 = return() 
sample n = do 
    x <- await 
    lift $ print x 
    sample (n-1) 

sampleSomeInts :: Effect ReadGenIO() 
sampleSomeInts = hoist toReader ints >-> hoist lift (sample 1000) 

runReadGenE :: Effect ReadGenIO a -> IO a 
runReadGenE = withSystemRandom . runReaderT . runEffect 

example :: IO() 
example = runReadGenE sampleSomeInts 

有一個在Pipes.Lift另一套工具,管道的用戶應該知道的。這些是通過分佈在Proxy上運行變形金剛像Random monad的工具。這裏有預建的工具用於運行變壓器庫中熟悉的變壓器。它們全部由distribute構建而成。它將Proxy ... (t m) a變成t (Proxy ... m) a,您可以使用您用來運行t的任何工具運行一次

import Pipes.Lift 

runRandomP :: PrimMonad m => Proxy a a' b b' Random r -> 
          Gen (PrimState m) -> Proxy a a' b b' m r 
runRandomP = runReaderT . distribute . hoist toReader 

可以完成管道結合在一起,並使用runEffect擺脫Proxy S的,但你會被自己玩弄發電機參數爲您結合Proxy ... IO rš在一起。

+0

剛剛實施這個,我肯定喜歡這個解決方案。謝謝! –