haskell-notes

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

[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, по которому следующая функция может опознать ошибку и предпринять

какие-нибудь действия. Так обрабатываются ошибки в чистых функциях. В этом разделе мы узнаем о том,

как обрабатываются ошибки, которые происходят при взаимодействии с внешним миром, ошибки, которые

происходят внутри типа IO.

Ошибки функций с побочными эффектами обрабатываются с помощью специальной функции catch, она

определена в Prelude:

catch :: IO a -> (IOError -> IO a) -> IO a

Эта функция принимает значение, которое содержит побочные эффекты и функцию, которая обрабаты-

вает исключительные ситуации. К примеру если мы попытаемся прочитать данные из файла, к которому у

нас нет доступа, произойдёт ошибка. Мы можем не дать программе упасть и обработать ошибку с помощью

Читай продолжение на следующей странице
Добавить комментарии