mwc-random 包提供了两个PrimMonad 实例,一个用于IO,另一个用于ST s。只要在所有状态标签s 上参数化ST 计算,我们就可以运行计算并使用runST :: (forall s. ST s a) -> a 提取值。这本身不会很有用,因为我们会丢失状态:随机生成器的种子,但 mwc-random 也提供了handle the seeds 的显式方法:
save :: PrimMonad m => Gen (PrimState m) -> m Seed
restore :: PrimMonad m => Seed -> m (Gen (PrimState m))
只要生成器位于forall s. ST s 中,我们就可以使用这些计算从生成单个值的计算中生成值流。
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
import System.Random.MWC
import Control.Monad.ST
import System.Random.MWC.Distributions
randomStream :: forall s a. (forall s. GenST s -> ST s a) -> GenST s -> ST s [a]
randomStream item = go
where
go :: forall s. GenST s -> ST s [a]
go gen = do
x <- item gen
seed <- save gen
return (x:runST (restore seed >>= go))
有了这个,我们可以把你的例子写成
main = do
bits <- withSystemRandom (randomStream (bernoulli 0.25))
print . take 32 $ bits
我们实际上可以构建比为流中的每个项目使用相同的生成器更复杂的生成器。我们可以沿着流线程化一个状态,这样每个值都可以依赖于前一个值的结果。
unfoldStream :: forall s a b. (forall s. b -> GenST s -> ST s (a, b)) -> b -> GenST s -> ST s [a]
unfoldStream item = go
where
go :: forall s. b -> GenST s -> ST s [a]
go b gen = do
(x,b') <- item b gen
seed <- save gen
return (x:runST (restore seed >>= go b'))
以下示例流的结果在每次结果为 False 时的可能性都会增加。
import Control.Monad.Primitive
interesting :: (PrimMonad m) => Double -> Gen (PrimState m) -> m (Bool, Double)
interesting p gen = do
result <- bernoulli p gen
let p' = if result then p else p + (1-p)*0.25
return (result, p')
main = do
bits <- withSystemRandom (unfoldStream interesting 0)
print . take 32 $ bits