我有 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) . 我怀疑这是由于提取了一个禁止垃圾收集器的列表,这似乎是Pipes的一个单一用法:
惯用管道样式在生成元素时立即使用元素,而不是将所有元素加载到内存中 .
当 Producer
超过某个随机monad(即 Random
)并且我想要产生的效果是 IO
时,正确使用这种管道的方法是什么?
换句话说,我想将 Producer (Policy s a) Random x
插入 Consumer (Policy s a) IO x
.
1 回答
Random
是读取生成器的读取器我们可以将
Random a
简单地转换为ReaderT (Gen (PrimState m)) m a
. 这个简单的操作就是hoist
将Producer ... Random a
转换为Producer ... IO a
.由于
toReader
是微不足道的,因此它不会产生任何随机的生成开销 . 编写此函数只是为了演示其类型签名 .这里有两种方法 . 简单的方法是
hoist
将Consumer
放入同一个monad,将管道组合在一起,然后运行它们 .管道用户应该注意
Pipes.Lift
中的另一组工具 . 这些是运行变形金刚的工具,例如Random
monad,它通过Proxy
分发 . 这里有预先构建的工具,用于运行变压器库中熟悉的变压器 . 它们都是由distribute构建的 . 它将Proxy ... (t m) a
转换为t (Proxy ... m) a
,您可以使用用于运行t
的任何工具运行一次 .您可以将管道组合在一起并使用
runEffect
来摆脱Proxy
,但是当您将Proxy ... IO r
组合在一起时,您将自己处理生成器参数 .