Как попробовать RVarT в IO - PullRequest
1 голос
/ 02 мая 2019

У меня проблемы с оборачиванием моего мозга вокруг RVarT в случайном фу. Как умственное упражнение, я пытаюсь генерировать 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, и я не могу понять, имеет ли это смысл или я делаю что-то несостоятельное для начала.

1 Ответ

5 голосов
/ 03 мая 2019

Да, вы в значительной степени делаете что-то нездоровое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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...