繁体   English   中英

如何通过IO操作在某些非IO monad中惯用且高效地使用Pipe?

[英]How can I idiomatically and efficiently consume a Pipe in some non-IO monad, with an IO action?

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

policies :: Producer (Policy s a) Random x

Randommwc-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者通过简单的强化学习算法产生更好和更好的政策。

通过索引到policies ,我可以在比如5,000,000次迭代之后有效地绘制policies

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

我现在想要在每500,000步骤上绘制中间策略,以了解它们如何收敛。 我编写了一些函数,它们接受policies生成器并提取一个列表( [Policy sa] ),例如10个策略 - 每500,000次迭代一次 - 然后绘制所有这些函数。

然而,这些函数需要更长的时间(10x)并使用更多的内存(4x),而不仅仅是如上所述绘制最终策略,即使学习迭代的总数应该相同(即5,000,000)。 我怀疑这是由于提取了一个禁止垃圾收集器的列表,这似乎是对管道的一种单一使用:

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

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

换句话说,我想将Producer (Policy sa) Random x插入Consumer (Policy sa) IO x

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)) ma 这个简单的操作就是你想要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

这里有两种方法。 简单的方法是将您的Consumer hoist到同一个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 ... (tm) 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 s组合在一起时,您将自己处理生成器参数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM