Создавайте случайные числа воспроизводимым способом и скрывайте потоки генератора (используя Haskell Monad) - PullRequest
0 голосов
/ 12 июня 2019

Мне нужно создать случайные данные в Haskell. Я хочу, чтобы мой код был: а) воспроизводимый из семян б) потоки генераторов должны быть неявными

Я в целом понимаю монады и то, как работают случайные генераторы. Мой подход состоит в том, чтобы пропустить генератор через код, чтобы я мог воспроизвести случайные числа, но хочу скрыть потоки генераторов в монаде. Я думаю, что Государственная Монада - это хороший подход.

Вот простой код:

type Gen a = State StdGen a

roll :: Gen Int
roll = state $ randomR (1, 6)

roll2 :: Gen Int
roll2 = (+) <$> roll <*> roll

test :: Int -> IO ()
test seed = do
  let gen = mkStdGen seed
  print (evalState roll gen)
  print (evalState roll gen)
  print (evalState roll2 gen)
  print (evalState roll2 gen)

Я пытаюсь использовать State, чтобы я мог протолкнуть резьбу генератора в State Monad, но результаты броска такие же, как и результаты roll2. Я вижу, что это потому, что я передаю gen в функции несколько раз, поэтому, конечно, он будет выдавать один и тот же результат. Это заставляет меня думать, что мне нужно получить новый генератор от каждой функции. Но затем я вернулся к тому, чтобы пропустить генератор через код, чего я пытаюсь избежать, используя State. Я чувствую, что мне не хватает трюка!

Я тоже исследовал MonadRandom, и это оттолкнуло многопоточность от моего кода, но я не мог понять, как сделать этот подход воспроизводимым.

Я много охотился и много чего пробовал, но, похоже, всегда либо смог скрыть генераторы, либо сделать код воспроизводимым, но не оба одновременно.

Я хочу использовать монаду более специфичную, чем IO.

Я также собираюсь создать серию более сложных функций, которые будут генерировать случайные списки чисел, поэтому мне нужен простой способ, чтобы эти случайные функции зависели друг от друга. Я справился с этим с MonadRandom, но снова я не мог понять, как это можно воспроизвести.

Любая помощь приветствуется.

Ответы [ 2 ]

5 голосов
/ 12 июня 2019

Если вам не нужно чередовать IO со случайностью, как здесь, тогда ответом будет просто объединить ваши State действия в одно с операциями Monad (они - вещь, передающая состояние длявы!).

test :: Int -> IO ()
test seed = do
  print a
  print b
  print c
  print d
  where
  (a,b,c,d) = flip evalState (mkStdGen seed) $ do
    a <- roll
    b <- roll
    c <- roll2
    d <- roll2
    return (a,b,c,d)

Если вам нужно будет чередовать IO и случайность, то вам нужно рассмотреть StateT StdGen IO как вашу монаду вместо использования State StdGen и IO по отдельности.Это может выглядеть так, скажем:

roll :: MonadState StdGen m => m Int
roll = state (randomR (1,6))

roll2 :: MonadState StdGen m => m Int
roll2 = (+) <$> roll <*> roll

test :: (MonadState StdGen m, MonadIO m) => m ()
test = do
  roll >>= liftIO . print
  roll >>= liftIO . print
  roll2 >>= liftIO . print
  roll2 >>= liftIO . print

(Вы можете затем использовать, например, evalStateT test (mkStdGen seed), чтобы превратить это обратно в действие IO (), или встроить его в более крупное вычисление, если есть другие случайные вещивам нужно было сгенерировать и выполнить IO about.)

MonadRandom делает чуть больше, чем упаковывает StateT StdGen таким образом, что вы все еще можете использовать состояние non-seed, поэтому я призываю вас пересмотреть использованиеЭто.evalRand и evalRandT из Control.Monad.Random.Lazy (или .Strict) должны дать вам необходимую повторяемость;если они этого не делают, вам следует открыть новый вопрос с полной информацией о том, что вы пытались и как это пошло не так.

0 голосов
/ 12 июня 2019

Обычно весь случайный генератор состоит в том, что вы не всегда получаете один и тот же результат.И именно поэтому вы используете монаду состояния: для передачи обновленного генератора , чтобы следующее случайное событие действительно было другим.

Если вы хотите всегда одно и то же значение, тогдана самом деле нет никакой причины использовать специальные случайные инструменты вообще - просто сгенерируйте одно значение один раз (или два значения), а затем передайте его везде, где необходимо, как если бы вы передавали другую переменную.

test :: IO ()
test = do
   [dice0, dice1] <- replicateM 2 $ randomRIO (1,6)
   print dice0
   print dice0
   print $ dice0+dice1
   print $ dice0+dice1
...