如何在 IO 中采样 RVarT
How to sample RVarT in IO
我很难在 random-fu 中思考 RVarT
。作为一项脑力练习,我正在尝试使用 monad 转换器
随机生成 Maybe x
并将它们组合成 Maybe (x, x)
我设法做到了这一点,这对我来说似乎很直观
maybeRandSome :: (MaybeT RVar) Int
maybeRandSome = lift $ return 1
maybeRandNone :: (MaybeT RVar) Int
maybeRandNone = MaybeT . return $ Nothing
maybeTwoRands :: (MaybeT RVar) (Int, Int)
maybeTwoRands =
do
x <- maybeRandSome
y <- maybeRandNone
return (x, y)
并且可以在 IO 中对它们进行采样
> sample $ runMaybeT maybeTwoRands
Nothing
但是我不知道是否可以反过来:
reverseMaybeRandSome :: (RVarT Maybe) Int
reverseMaybeRandSome = lift $ Just 1
reverseMaybeRandNone :: (RVarT Maybe) Int
reverseMaybeRandNone = lift Nothing
reverseMaybeTwoRands :: (RVarT Maybe) (Int, Int)
reverseMaybeTwoRands =
do
x <- Random.sample reverseMaybeRandSome
y <- Random.sample reverseMaybeRandNone
return (x, y)
这需要我以某种方式从 Maybe m
提升到 MonadRandom m
,我不知道这是否有意义,或者我是否在做一些不合理的事情。
是的,你几乎在做一些不合理的事情。 MaybeT m a
同构于任何 monad 的 m (Maybe a)
,包括 m = RVar
,所以 MaybeT RVar a
实际上只是一个 RVar (Maybe a)
,它是一个随机变量的表示Maybe a
中的值。鉴于此,很容易想象对两个 Maybe a
值的随机变量进行采样并以通常的方式将它们组合成一个 Maybe (a,a)
值的随机变量(即,如果其中一个或两个都是 Nothing
,则结果为Nothing
,如果分别为Just x
和Just y
,则结果为Just (x,y)
)。这就是您的第一段代码所做的。
然而,RVarT Maybe a
不同。这是一个 a
值( 不是 Maybe a
值)随机变量,可以 使用基础 Maybe
monad 的设施 在生成它的值时,前提是它们可以以某种合理的方式提升到最终的单子,其中实现了随机变量的 "randomness"。
要理解这意味着什么,我们必须更详细地了解类型 RVar
和 RVarT
。
类型 RVar a
表示一个 a
值的随机变量。为了实际将此表示转换为真正的随机值,您必须 运行 它具有:
runRVar :: RandomSource m s => RVar a -> s -> m a
这有点太笼统了,所以想象一下它专门用于:
runRVar :: RVar a -> StdRandom -> IO a
注意这里StdRandom
是唯一有效的StdRandom
,所以我们总是写runRVar something StdRandom
,也可以写成sample something
.
有了这个专业化,您应该将 RVar a
视为 使用一组有限的随机化原语 构建随机变量的单子配方 runRVar
转换为实现相对于全局随机数生成器的随机化原语的 IO
动作。这种到 IO 操作的转换允许配方生成实际采样的随机值。如果您有兴趣,可以在 Data.Random.Internal.Source
.
中找到有限的随机化原语集
类似地,RVarT n a
也是一个 a
值的随机变量(即,使用一组有限的随机化原语构建随机变量的方法)也可以访问 "facilities of another base monad n
"。这个配方可以运行在任何可以实现两者随机化原语和基础monad[=53]的最终monad中=].在一般情况下,你 运行 它与:
runRVarTWith :: MonadRandom m =>
(forall t. n t -> m t) -> RVarT n a -> s -> m a
它采用显式提升函数来解释 如何 将基础 monad n
的设施提升到最终 monad m
.
如果基础 monad n
是 Maybe
,那么它 "facilities" 能够发出错误或计算失败的信号。您可能会使用这些工具来构造以下有点愚蠢的随机变量:
sqrtNormal :: RVarT Maybe Double
sqrtNormal = do
z <- stdNormalT
if z < 0
then lift Nothing -- use Maybe facilities to signal error
else return $ sqrt z
请注意,至关重要的是,sqrtNormal
并不代表要生成的 Maybe Double
值随机变量。相反,它表示 Double
值的随机变量,其生成可能会通过基础 Maybe
monad 的设施失败。
为了实现这个随机变量(即对其进行采样),我们需要在适当的最终 monad 中 运行 它。最终的 monad 需要支持随机化原语 和 从 Maybe
monad 中适当提升失败的概念。
IO 工作正常,如果适当的失败概念是 运行时间错误:
liftMaybeToIO :: Maybe a -> IO a
liftMaybeToIO Nothing = error "simulation failed!"
liftMaybeToIO (Just x) = return x
之后:
main1 :: IO ()
main1 = print =<< runRVarTWith liftMaybeToIO sqrtNormal StdRandom
大约一半时间生成正标准高斯的平方根,另一半时间抛出 运行时间错误。
如果你想以纯形式捕获失败(例如 Maybe
),那么你需要考虑在适当的 monad 中实现 RVar
。单子:
MaybeT IO a
会成功的。它与 IO (Maybe a)
同构,因此它具有可用的 IO 设施(需要实现随机化原语)并且能够通过 returning Nothing
发出失败信号。如果我们写:
main2 :: IO ()
main2 = print =<< runMaybeT act
where act :: MaybeT IO Double
act = sampleRVarTWith liftMaybe sqrtNormal
我们会得到一个错误,提示 MonadRandom (MaybeT IO)
没有实例。我们可以创建一个如下:
import Control.Monad.Trans (liftIO)
instance MonadRandom (MaybeT IO) where
getRandomPrim = liftIO . getRandomPrim
加上适当的提升功能:
liftMaybe :: Maybe a -> MaybeT IO a
liftMaybe = MaybeT . return
之后,main2
将 return Nothing
大约一半的时间和 Just
正高斯的平方根另一半。
完整代码:
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Data.Random
import Data.Random.Lift
import Data.Random.Internal.Source
sqrtNormal :: RVarT Maybe Double
sqrtNormal = do
z <- stdNormalT
if z < 0
then lift Nothing -- use Maybe facilities to signal error
else return $ sqrt z
liftMaybeToIO :: Maybe a -> IO a
liftMaybeToIO Nothing = error "simulation failed!"
liftMaybeToIO (Just x) = return x
main1 :: IO ()
main1 = print =<< runRVarTWith liftMaybeToIO sqrtNormal StdRandom
instance MonadRandom (MaybeT IO) where
getRandomPrim = liftIO . getRandomPrim
main2 :: IO ()
main2 = print =<< runMaybeT act
where act :: MaybeT IO Double
act = runRVarTWith liftMaybe sqrtNormal StdRandom
liftMaybe :: Maybe a -> MaybeT IO a
liftMaybe = MaybeT . return
这将全部应用于您的第二个示例的方式看起来像这样,它将始终打印 Nothing
:
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Data.Random
import Data.Random.Lift
import Data.Random.Internal.Source
reverseMaybeRandSome :: RVarT Maybe Int
reverseMaybeRandSome = return 1
reverseMaybeRandNone :: RVarT Maybe Int
reverseMaybeRandNone = lift Nothing
reverseMaybeTwoRands :: RVarT Maybe (Int, Int)
reverseMaybeTwoRands =
do
x <- reverseMaybeRandSome
y <- reverseMaybeRandNone
return (x, y)
instance MonadRandom (MaybeT IO) where
getRandomPrim = liftIO . getRandomPrim
runRVarTMaybe :: RVarT Maybe a -> IO (Maybe a)
runRVarTMaybe act = runMaybeT $ runRVarTWith liftMaybe act StdRandom
where
liftMaybe :: Maybe a -> MaybeT IO a
liftMaybe = MaybeT . return
main :: IO ()
main = print =<< runRVarTMaybe reverseMaybeTwoRands
我很难在 random-fu 中思考 RVarT
。作为一项脑力练习,我正在尝试使用 monad 转换器
Maybe x
并将它们组合成 Maybe (x, x)
我设法做到了这一点,这对我来说似乎很直观
maybeRandSome :: (MaybeT RVar) Int
maybeRandSome = lift $ return 1
maybeRandNone :: (MaybeT RVar) Int
maybeRandNone = MaybeT . return $ Nothing
maybeTwoRands :: (MaybeT RVar) (Int, Int)
maybeTwoRands =
do
x <- maybeRandSome
y <- maybeRandNone
return (x, y)
并且可以在 IO 中对它们进行采样
> sample $ runMaybeT maybeTwoRands
Nothing
但是我不知道是否可以反过来:
reverseMaybeRandSome :: (RVarT Maybe) Int
reverseMaybeRandSome = lift $ Just 1
reverseMaybeRandNone :: (RVarT Maybe) Int
reverseMaybeRandNone = lift Nothing
reverseMaybeTwoRands :: (RVarT Maybe) (Int, Int)
reverseMaybeTwoRands =
do
x <- Random.sample reverseMaybeRandSome
y <- Random.sample reverseMaybeRandNone
return (x, y)
这需要我以某种方式从 Maybe m
提升到 MonadRandom m
,我不知道这是否有意义,或者我是否在做一些不合理的事情。
是的,你几乎在做一些不合理的事情。 MaybeT m a
同构于任何 monad 的 m (Maybe a)
,包括 m = RVar
,所以 MaybeT RVar a
实际上只是一个 RVar (Maybe a)
,它是一个随机变量的表示Maybe a
中的值。鉴于此,很容易想象对两个 Maybe a
值的随机变量进行采样并以通常的方式将它们组合成一个 Maybe (a,a)
值的随机变量(即,如果其中一个或两个都是 Nothing
,则结果为Nothing
,如果分别为Just x
和Just y
,则结果为Just (x,y)
)。这就是您的第一段代码所做的。
然而,RVarT Maybe a
不同。这是一个 a
值( 不是 Maybe a
值)随机变量,可以 使用基础 Maybe
monad 的设施 在生成它的值时,前提是它们可以以某种合理的方式提升到最终的单子,其中实现了随机变量的 "randomness"。
要理解这意味着什么,我们必须更详细地了解类型 RVar
和 RVarT
。
类型 RVar a
表示一个 a
值的随机变量。为了实际将此表示转换为真正的随机值,您必须 运行 它具有:
runRVar :: RandomSource m s => RVar a -> s -> m a
这有点太笼统了,所以想象一下它专门用于:
runRVar :: RVar a -> StdRandom -> IO a
注意这里StdRandom
是唯一有效的StdRandom
,所以我们总是写runRVar something StdRandom
,也可以写成sample something
.
有了这个专业化,您应该将 RVar a
视为 使用一组有限的随机化原语 构建随机变量的单子配方 runRVar
转换为实现相对于全局随机数生成器的随机化原语的 IO
动作。这种到 IO 操作的转换允许配方生成实际采样的随机值。如果您有兴趣,可以在 Data.Random.Internal.Source
.
类似地,RVarT n a
也是一个 a
值的随机变量(即,使用一组有限的随机化原语构建随机变量的方法)也可以访问 "facilities of another base monad n
"。这个配方可以运行在任何可以实现两者随机化原语和基础monad[=53]的最终monad中=].在一般情况下,你 运行 它与:
runRVarTWith :: MonadRandom m =>
(forall t. n t -> m t) -> RVarT n a -> s -> m a
它采用显式提升函数来解释 如何 将基础 monad n
的设施提升到最终 monad m
.
如果基础 monad n
是 Maybe
,那么它 "facilities" 能够发出错误或计算失败的信号。您可能会使用这些工具来构造以下有点愚蠢的随机变量:
sqrtNormal :: RVarT Maybe Double
sqrtNormal = do
z <- stdNormalT
if z < 0
then lift Nothing -- use Maybe facilities to signal error
else return $ sqrt z
请注意,至关重要的是,sqrtNormal
并不代表要生成的 Maybe Double
值随机变量。相反,它表示 Double
值的随机变量,其生成可能会通过基础 Maybe
monad 的设施失败。
为了实现这个随机变量(即对其进行采样),我们需要在适当的最终 monad 中 运行 它。最终的 monad 需要支持随机化原语 和 从 Maybe
monad 中适当提升失败的概念。
IO 工作正常,如果适当的失败概念是 运行时间错误:
liftMaybeToIO :: Maybe a -> IO a
liftMaybeToIO Nothing = error "simulation failed!"
liftMaybeToIO (Just x) = return x
之后:
main1 :: IO ()
main1 = print =<< runRVarTWith liftMaybeToIO sqrtNormal StdRandom
大约一半时间生成正标准高斯的平方根,另一半时间抛出 运行时间错误。
如果你想以纯形式捕获失败(例如 Maybe
),那么你需要考虑在适当的 monad 中实现 RVar
。单子:
MaybeT IO a
会成功的。它与 IO (Maybe a)
同构,因此它具有可用的 IO 设施(需要实现随机化原语)并且能够通过 returning Nothing
发出失败信号。如果我们写:
main2 :: IO ()
main2 = print =<< runMaybeT act
where act :: MaybeT IO Double
act = sampleRVarTWith liftMaybe sqrtNormal
我们会得到一个错误,提示 MonadRandom (MaybeT IO)
没有实例。我们可以创建一个如下:
import Control.Monad.Trans (liftIO)
instance MonadRandom (MaybeT IO) where
getRandomPrim = liftIO . getRandomPrim
加上适当的提升功能:
liftMaybe :: Maybe a -> MaybeT IO a
liftMaybe = MaybeT . return
之后,main2
将 return Nothing
大约一半的时间和 Just
正高斯的平方根另一半。
完整代码:
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Data.Random
import Data.Random.Lift
import Data.Random.Internal.Source
sqrtNormal :: RVarT Maybe Double
sqrtNormal = do
z <- stdNormalT
if z < 0
then lift Nothing -- use Maybe facilities to signal error
else return $ sqrt z
liftMaybeToIO :: Maybe a -> IO a
liftMaybeToIO Nothing = error "simulation failed!"
liftMaybeToIO (Just x) = return x
main1 :: IO ()
main1 = print =<< runRVarTWith liftMaybeToIO sqrtNormal StdRandom
instance MonadRandom (MaybeT IO) where
getRandomPrim = liftIO . getRandomPrim
main2 :: IO ()
main2 = print =<< runMaybeT act
where act :: MaybeT IO Double
act = runRVarTWith liftMaybe sqrtNormal StdRandom
liftMaybe :: Maybe a -> MaybeT IO a
liftMaybe = MaybeT . return
这将全部应用于您的第二个示例的方式看起来像这样,它将始终打印 Nothing
:
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Data.Random
import Data.Random.Lift
import Data.Random.Internal.Source
reverseMaybeRandSome :: RVarT Maybe Int
reverseMaybeRandSome = return 1
reverseMaybeRandNone :: RVarT Maybe Int
reverseMaybeRandNone = lift Nothing
reverseMaybeTwoRands :: RVarT Maybe (Int, Int)
reverseMaybeTwoRands =
do
x <- reverseMaybeRandSome
y <- reverseMaybeRandNone
return (x, y)
instance MonadRandom (MaybeT IO) where
getRandomPrim = liftIO . getRandomPrim
runRVarTMaybe :: RVarT Maybe a -> IO (Maybe a)
runRVarTMaybe act = runMaybeT $ runRVarTWith liftMaybe act StdRandom
where
liftMaybe :: Maybe a -> MaybeT IO a
liftMaybe = MaybeT . return
main :: IO ()
main = print =<< runRVarTMaybe reverseMaybeTwoRands