10

我有一个使用我自己的monadProducer创建依赖于随机性的值:Random

policies :: Producer (Policy s a) Random x

Randommwc-random是一个可以从STor运行的包装器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简单的强化学习算法产生越来越好的策略。

我可以通过索引到 5,000,000 次迭代后有效地绘制策略policies

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

我现在想在每 500,000 步上绘制中间策略,看看它们是如何收敛的。我写了几个函数,它们获取生产者policies并提取一个列表([Policy s a]

然而,这些函数花费的时间(10 倍)和使用更多的内存(4 倍)比仅仅绘制上面的最终策略,即使学习迭代的总数应该是相同的(即 5,000,000)。我怀疑这是由于提取了一个禁止垃圾收集器的列表,这似乎是管道的一种惯用用法:

惯用的管道样式在生成元素时立即使用它们,而不是将所有元素加载到内存中。

Producer超过一些随机单子(即Random)并且我想要产生的效果时,使用这样的管道的正确方法是IO什么?

换句话说,我想将 aProducer (Policy s a) Random x插入到Consumer (Policy s a) IO x.

4

1 回答 1

2

Random是读取生成器的读取器

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

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

我们可以简单地将 a 转换Random a为 a ReaderT (Gen (PrimState m)) m a。这个微不足道的操作就是您想要hoist将 aProducer ... Random a变成 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管道用户应该注意另一组工具。这些是Random通过将其分布在Proxy. 这里有预先构建的工具,用于运行来自变形金刚库的熟悉的变形金刚。它们都是由distribute. 它将 aProxy ... (t m) a变成 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来摆脱Proxys,但是当您将Proxy ... IO rs 组合在一起时,您会自己处理生成器参数。

于 2016-09-10T07:53:03.620 回答