Да, вы в значительной степени делаете что-то нездоровоеMaybeT m a
изоморфно 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
монады при генерации ее значений при условии, что они могут бытькаким-то разумным образом поднята к последней монаде, в которой реализована «случайность» случайной величины.
Чтобы понять, что это значит, нам нужно более детально взглянуть на типы 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
действий, которые реализуют примитивы рандомизации относительно глобального генератора случайных чисел.Это преобразование в действия ввода-вывода - это то, что позволяет рецепту генерировать фактическую выборочную случайную величину.Если вам интересно, вы можете найти ограниченный набор примитивов рандомизации в Data.Random.Internal.Source
.
Аналогично, RVarT n a
также является a
-значной случайной величиной (т. Е.рецепт для построения случайной величины с использованием ограниченного набора примитивов рандомизации), который также имеет доступ к «средствам другой базовой монады n
».Этот рецепт может быть запущен в любой последней монаде, которая может реализовать и примитивы рандомизации и возможности базовой монады n
.В общем случае вы запускаете его с:
runRVarTWith :: MonadRandom m =>
(forall t. n t -> m t) -> RVarT n a -> s -> m a
, который принимает явную функцию подъема, которая объясняет как поднять средства базовой монады n
до конечной монады m
.
Если базовая монада n
равна Maybe
, то ее «средства» - это способность сигнализировать об ошибке или неудачном вычислении.Вы можете использовать эти возможности для создания следующей несколько глупой случайной переменной:
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
монады.
Чтобы реализовать эту случайную переменную (то есть сэмплировать ее), нам нужно запустить еев соответствующей финальной монаде.Последняя монада должна поддерживать как примитивы рандомизации , так и соответствующим образом снятое понятие сбоя из монады Maybe
.
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
), то вам нужно рассмотреть реализацию RVar
в соответствующей монаде.Монада:
MaybeT IO a
сделает свое дело.Он изоморфен IO (Maybe a)
, поэтому имеет доступные средства ввода-вывода (необходимые для реализации примитивов рандомизации) и способен сигнализировать о сбое, возвращая 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
вернет 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