如何在IO中采样RVarT

Sam*_*vas 1 random haskell monad-transformers

我在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)
Run Code Online (Sandbox Code Playgroud)

并可以在IO中进行采样

> sample $ runMaybeT maybeTwoRands
Nothing
Run Code Online (Sandbox Code Playgroud)

但是我无法弄清楚是否可以逆转:

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)
Run Code Online (Sandbox Code Playgroud)

这需要我从电梯Maybe mMonadRandom m不知何故,我想不通,如果是有道理的,或者如果我做一些不健全的开始。

K. *_*uhr 5

是的,您几乎在做一些不合理的事情。 对任何monad MaybeT m a都是同构的m (Maybe a),包括m = RVar,所以a MaybeT RVar a实际上只是an RVar (Maybe a),它表示采用的值的随机变量Maybe a。有了这个,就很容易想象对两个Maybe a值的随机变量进行采样,然后Maybe (a,a)以通常的方式将它们组合为一个值随机变量(即,如果其中一个或两个都是Nothing,结果为Nothing,如果它们分别为Just xJust y,是Just (x,y))。这就是您的第一段代码正在做的事情。

但是,RVarT Maybe a是不同的。它是一个有a值( Maybe a值)随机变量,可以使用基本Maybe单子的设施生成其值,只要可以以某种明智的方式将其提升到最终单子即可,在该单子中可以实现随机变量的“随机性” 。

要了解这意味着什么,我们必须更详细地研究类型RVarRVarT

该类型RVar a表示一个a值随机变量。为了将这种表示形式实际转换为真正的随机值,必须使用以下命令运行它:

runRVar :: RandomSource m s => RVar a -> s -> m a
Run Code Online (Sandbox Code Playgroud)

这有点太笼统了,所以可以想象它专门用于:

runRVar :: RVar a -> StdRandom -> IO a
Run Code Online (Sandbox Code Playgroud)

请注意,这StdRandomStdRandom此处唯一有效的值,因此我们将始终写入runRVar something StdRandom,也可以写入sample something

有了这种专业化,您应该查看RVar a一元配方使用一组有限的随机原语构建一个随机变量runRVar转换成IO即实现随机元相对于一个全局随机数发生器作用。这种到IO操作的转换使配方可以生成实际的采样随机值。如果您有兴趣,可以在中找到有限的随机基元集Data.Random.Internal.Source

类似地,RVarT n a也是一个具有a值的随机变量(即,使用有限的一组随机化基元来构造随机变量的配方),该值也可以访问“另一个基本monad的功能n”。这个配方可以,可以实现任何最终单子内部运行两个随机化原语基单子的设施n。在一般情况下,您可以使用以下命令运行它:

runRVarTWith :: MonadRandom m => 
    (forall t. n t -> m t) -> RVarT n a -> s -> m a
Run Code Online (Sandbox Code Playgroud)

它具有明确的提升功能,解释了如何将基本单子的设施提升n到最终单子m

如果基本monad nMaybe,则它的“便利性”是发出错误或计算失败的信号。您可能会使用这些工具来构造以下有点愚蠢的随机变量:

sqrtNormal :: RVarT Maybe Double
sqrtNormal = do
  z <- stdNormalT
  if z < 0
    then lift Nothing   -- use Maybe facilities to signal error
    else return $ sqrt z
Run Code Online (Sandbox Code Playgroud)

请注意,重要的是,sqrtNormal它不代表Maybe Double要生成的值随机变量。相反,它表示Double带有值的随机变量,其生成可能会由于基本Maybemonad 的功能而失败。

为了实现此随机变量(即对其进行采样),我们需要在适当的最终monad中运行它。最终的monad需要同时支持随机基元Maybemonad中适当提升的失败概念。

如果适当的故障概念是运行时错误,则IO可以正常工作:

liftMaybeToIO :: Maybe a -> IO a
liftMaybeToIO Nothing = error "simulation failed!"
liftMaybeToIO (Just x) = return x
Run Code Online (Sandbox Code Playgroud)

之后:

main1 :: IO ()
main1 = print =<< runRVarTWith liftMaybeToIO sqrtNormal StdRandom
Run Code Online (Sandbox Code Playgroud)

大约一半时间生成正标准高斯的平方根,另一半抛出运行时错误。

如果要以纯形式(Maybe例如以)捕获故障,则需要考虑RVar以适当的monad 实现。单子:

MaybeT IO a
Run Code Online (Sandbox Code Playgroud)

会成功的 它与相同IO (Maybe a),因此具有可用的IO设施(需要实现随机化原语),并能够通过return来指示失败Nothing。如果我们写:

main2 :: IO ()
main2 = print =<< runMaybeT act
  where act :: MaybeT IO Double
        act = sampleRVarTWith liftMaybe sqrtNormal
Run Code Online (Sandbox Code Playgroud)

我们会收到一个错误,指出没有实例MonadRandom (MaybeT IO)。我们可以如下创建一个:

import Control.Monad.Trans (liftIO)
instance MonadRandom (MaybeT IO) where
  getRandomPrim = liftIO . getRandomPrim
Run Code Online (Sandbox Code Playgroud)

以及适当的提升功能:

liftMaybe :: Maybe a -> MaybeT IO a
liftMaybe = MaybeT . return
Run Code Online (Sandbox Code Playgroud)

之后,main2将返回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
Run Code Online (Sandbox Code Playgroud)

这一切都适用于您的第二个示例的方式看起来像这样,它将始终打印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
Run Code Online (Sandbox Code Playgroud)