我有Producer
创建依赖于随机性的值,使用我自己的Random
monad:
policies :: Producer (Policy s a) Random x
Random
是mwc-random
的封套,可以从ST
或IO
运行:
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
生产者通过简单的强化学习算法产生更好,更好的策略。
我可以通过索引到policies
,在5,000,000次迭代之后有效地绘制策略:
Just convergedPolicy <- Random.runIO $ Pipes.index 5000000 policies
plotPolicy convergedPolicy "policy.svg"
我现在想要在每500,000步骤上绘制中间策略,以了解它们如何收敛。我编写了几个函数,它们接受policies
生成器并提取一个列表([Policy s a]
),例如10个策略 - 每500,000次迭代一次 - 然后绘制所有这些函数。
然而,这些函数需要更长的时间(10x)并使用更多的内存(4x),而不仅仅是如上所述绘制最终策略,即使学习迭代的总数应该相同(即5,000,000)。我怀疑这是因为提取了一个禁止垃圾收集器的列表,这似乎是对管道的一种单一使用:
惯用管道样式在生成元素时会立即使用这些元素,而不是将所有元素加载到内存中。
当Producer
超过某个随机monad(即Random
)并且我想要生成的效果位于{{1}时,正确使用这样的管道的方法是什么? }?
换句话说,我想将IO
插入Producer (Policy s a) Random x
。
答案 0 :(得分: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 a
简单地转换为ReaderT (Gen (PrimState m)) m a
。此普通操作是您hoist
将Producer ... 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
这里有两种方法。简单的方法是hoist
将Consumer
放入同一个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
中的另一组工具。这些是运行变形金刚的工具,例如Random
monad,通过Proxy
分发。这里有预先构建的工具,用于运行变压器库中熟悉的变压器。它们都是由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
,但是当您组合Proxy ... IO r
时,您将自己处理生成器参数一起来。