В духе https://xkcd.com/221/, вот «решение» без какого-либо ввода-вывода:
rng :: Int -> Int
rng upper
| upper<=4 = upper
| otherwise = 4
Так что вы получите «случайное число, соответствующее RFC 1149.5». Всегда четыре, если только это не выходит за пределы диапазона.
В чем проблема? Ну, ясно, что он всегда дает одно и то же число - и поэтому должно быть , потому что все функции Haskell должны быть функциями , то есть ссылочно-прозрачными . OTOH, случайное число генератор должен давать различное число каждый раз, когда вы вызываете его ... таким образом, не функция, и большинство других языков программированияпросто притвориться, что это функция с побочным эффектом - потому что у них нет подходящих средств для выражения того, что такое побочные эффекты. Что ж, у Haskell есть подходящие средства для выражения этого, и это монада IO: у вас могут быть вычисления, которые зависят от побочного эффекта, но ясно, что эти вычисления, если вы их запустите, сами будут иметь этот побочный эффект.
В этом свете подпись Int -> IO Int
имеет смысл для функции. (Это является функцией, но результатом является IO-действие и только при выполнении это действие дает вам Int
.)
Уродливо то, что IO Int
может буквально сделать что угодно в IO
- например, может запустить несколько ракет и вернуть вам количество жертв. Более реалистично, это могло легко изменить некоторый файл в Вашей домашней директории. Принимая во внимание, что вы на самом деле хотите всего лишь крошечный безобидный побочный эффект, которого достаточно, чтобы в следующий раз получить новое случайное число. Обычно генераторы случайных чисел в любом случае не являются случайными, а PRNG , которые сохраняют постоянную переменную состояния , которая обновляется случайным образом каждый раз, когда вы извлекаете значение. В следующий раз это состояние будет другим, и вы получите другое значение по желанию. Эта переменная состояния может храниться в IO
-mutable месте
import Data.IORef
type RandStV = Int
type RandSt = IORef RandStV
rng' :: RandSt -> Int -> IO Int
rng' rSt upper = do
x <- readIORef rSt
let x' = ((x * 1103515245) + 12345) `mod` 0x7fffffff -- https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/random_r.c;hb=glibc-2.26#l362
writeIORef rSt x'
return $ x `mod` upper
... или вы можете просто явно передать обновленное состояние вместе с результатом
rng'' :: Int -> RandStV -> (RandStV, Int)
rng'' upper x =
let x' = ((x * 1103515245) + 12345) `mod` 0x7fffffff
in (x', x `mod` upper)
... или его можно передать в выделенной монаде состояния , которая является еще одним способом записи передачи обновленной переменной:
type RandStM = State RandStV
rng''' :: Int -> RandStM Int
rng''' upper = do
x <- get
let x' = ((x * 1103515245) + 12345) `mod` 0x7fffffff
put x'
return $ x `mod` upper
См. пакет random-fu для полезных помощников по такой случайной монаде .
Один математический способ интерпретации rng'''
состоит в том, чтобы сказать, что это функция, котораяпринимает верхнюю границу в качестве аргумента и возвращает распределение чисел. Распределение всегда одинаково, но оно «содержит» много чисел вместе с вероятностью их появления. На самом деле генерирует целое число означает, что вы выборка из распределения.