компьютером, мы запрашиваем глобальное для системы случайное значение, поэтому возвращаемое значе-
ние будет завёрнуто в тип 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
[5,6,5,5,6,4,6,4,4,4]
Обратите внимание на то, что функция getStdGen не обновляет генератор случайных чисел. Мы запра-
шиваем глобальное состояние. Поэтому, дважды подбросив кубик, мы получили одни и те же результаты.
Генератор будет обновляться, если воспользоваться функцией newStdGen:
Prelude System.Random> fmap (take 10 . randomRs (1, 6)) newStdGen
[1,1,5,6,5,2,5,5,5,3]
Prelude System.Random> fmap (take 10 . randomRs (1, 6)) newStdGen
[5,4,6,5,5,5,1,5,5,2]
Создадим случайные слова из пяти букв:
Prelude System.Random> fmap (take 5 . randomRs (’a’, ’z’)) newStdGen
”maclg”
Prelude System.Random> fmap (take 5 . randomRs (’a’, ’z’)) newStdGen
”nfjoa”
Цитатник
Напишем небольшую программу, которая будет выводить на экран в случайном порядке цитаты. Цитаты
хранятся в виде списка пар (автор, высказывание). Сначала мы генерируем случайное число в диапазоне
длины списка, затем выбираем цитату под этим номером и выводим её на экран.
module Main where
import Control.Applicative
import System.Random
main =
format . (quotes !! ) randomRIO (0, length quotes — 1)
>>= putStrLn
format (a, b) = b
++ space ++ a ++ space
where space = ”nn”
quotes = [
(”Бьёрн Страуструп”,
”Есть лишь два вида языков программирования: те,
на которые вечно жалуются, и те, которые никогда
не используются.”),
(”Мохатма Ганди”, ”Ты должен быть теми изменениями, которые
ты хочешь видеть вокруг.”),
(”Сократ”, ”Я знаю лишь то, что ничего не знаю.”),
(”Китайская народная мудрость”, ”Сохранив спокойствие в минуту
гнева, вы можете избежать сотни дней сожалений”),
(”Жан Батист Мольер”, ”Медленно растущие деревья приносят лучшие плоды”),
(”Антуан де Сент-Экзюпери”, ”Жить это значит медленно рождаться”),
(”Альберт Эйнштейн”, ”Фантазия важнее знания.”),
(”Тони Хоар”, ”Внутри любой большой программы всегда есть
маленькая, что рвётся на свободу”),
(”Пифагор”, ”Не гоняйся за счастьем, оно всегда находится в тебе самом”),
(”Лао Цзы”, ”Путешествие в тысячу ли начинается с одного шага”)]
Функция format приводит цитату к виду приятному для чтения. Попробуем программу в интерпретаторе:
Prelude> :! ghc —make Quote -o hi
[1 of 1] Compiling Main
( Quote. hs, Quote. o )
Linking hi …
Prelude> :! ./hi
Путешествие в тысячу ли начинается с одного шага
Лао Цзы
Типичные задачи IO | 135
Prelude> :! ./hi
Не гоняйся за счастьем, оно всегда находится в тебе самом
Пифагор
Исключения
Мы уже знаем несколько типов, с помощью которых функции могут сказать, что что-то случилось не
так. Это типы Maybe и Either. Если функции не удалось вычислить значение она возвращает специальное
значение Nothing или Left reason, по которому следующая функция может опознать ошибку и предпринять
какие-нибудь действия. Так обрабатываются ошибки в чистых функциях. В этом разделе мы узнаем о том,