Функции для создания случайных значений определены в модуле System.Random. Модуль System.Random
входит в библиотеку random. Если в вашей поставке ghc его не оказалось, вы можете установить его вручную
через интернет, набрав в командной строке cabal install random. Сначала давайте разберёмся как гене-
рируются случайные числа. Стандартные случайные числа очень похожи на те, что были у нас, когда мы
рассматривали примеры специальных функций. У нас есть генератор случайных чисел типа g и с помощью
функции next мы можем получить обновлённый генератор и случайное целое число:
next :: g -> (Int, g)
Не правда ли этот тип очень похож на тип результата функций с состоянием. В качестве состояния теперь
выступает генератор случайных чисел g. Это поведение описывается классом RandomGen:
class RandomGen g where
next
:: g -> (Int, g)
split
:: g -> (g, g)
geтRange :: g -> (Int, Int)
Функция next обновляет генератор и возвращает случайное значение типа Int. Функция split раска-
лывает один генератор на два. Функция genRange возвращает диапазон значений генерируемых случайных
чисел. Первое значение в паре результата genRange должно быть всегда меньше второго. Для этого класса
определён один экземпляр, это тип StdGen. Мы можем создать первый генератор по целому числу с помощью
функции mkStdGen:
mkStdGen :: Int -> StdGen
Давайте посмотрим как это происходит в интерпретаторе:
Типичные задачи IO | 133
Prelude> :m System.Random
Prelude System.Random> let g0 = mkStdGen 0
Prelude System.Random> let (n0, g1) = next g0
Prelude System.Random> let (n1, g2) = next g1
Prelude System.Random> n0
2147482884
Prelude System.Random> n1
2092764894
Мы создали первый генератор, а затем начали получать новые. Для того, чтобы получать новые случайные
числа, нам придётся таскать везде за собой генератор случайных чисел. Мы можем обернуть его в функцию
с состоянием и пользоваться методами классов Functor, Applicative и Monad. Обновление генератора будет
происходить за ширмой, во время применения функций. Но у нас есть и другой путь.
Вместо монады State мы можем воспользоваться монадой IO. Если нам лень определять генератор слу-
чайных чисел, мы можем попросить компьютер определить его за нас. В этом случае мы взаимодействуем с
компьютером, мы запрашиваем глобальное для системы случайное значение, поэтому возвращаемое значе-
ние будет завёрнуто в тип IO. Для этого определены функции:
getStdGen :: IO StdGen
newStdGen :: IO StdGen
Функция getStdGen запрашивает глобальный для системы генератор случайных чисел. Функция
newStdGen не только запрашивает генератор, но также и обновляет его. Мы пользуемся этими функци-
ями так же как и mkStdGen, только теперь мы спрашиваем первый аргумент у компьютера, а не передаём его
вручную. Также есть ещё одна полезная функция:
getStdRandom
:: (StdGen -> (a, StdGen)) -> IO a
Посмотрим, что получится, если передать в неё функцию next:
Prelude System.Random> getStdRandom next
1386438055
Prelude System.Random> getStdRandom next
961860614
И не надо обновлять никаких генераторов. Но вместо одного неудобства мы получили другое. Теперь
значение завёрнуто в оболочку IO.
Генератор StdGen делает случайные числа из диапазона всех целых чисел. Что если мы хотим получить
только числа из некоторого интервала? И как получить случайные значения других типов? Для этого суще-
ствует класс Random. Он является удобной надстройкой над классом RandomGen. Посмотрим на его основные
методы:
class Random a where
randomR :: RandomGen g => (a, a) -> g -> (a, g)
random
:: RandomGen g => g -> (a, g)
Метод randomR принимает диапазон значений, генератор случайных чисел и возвращает случайное число
из указанного диапазона и обновлённый генератор. Метод random является синонимом метода next из класса
RandomGen, только теперь мы можем получать не только целые числа.
Есть и дополнительные методы. Есть методы, которые позволяют генерировать список всех возможных
случайных значений для данного генератора:
randomRs :: RandomGen g => (a, a) -> g -> [a]
randoms
:: RandomGen g => g -> [a]
За счёт лени мы будем получать новые значения по мере необходимости.
randomRIO
:: (a, a) -> IO a
randomIO
:: IO a
Эти функции выполняют тоже, что и основные функции класса, но им не нужен генератор случайных
чисел, они создают его с помощью функции getStdRandom. Экземпляры Random определены для Bool, Char,
Double, Float, Int и Integer. Например так мы можем подбросить кости десять раз:
134 | Глава 8: IO
Prelude System.Random> fmap (take 10 . randomRs (1, 6)) getStdGen
[5,6,5,5,6,4,6,4,4,4]
Prelude System.Random> fmap (take 10 . randomRs (1, 6)) getStdGen