элемент пары с помощью snd, мы выделяем остаток. Функция nextRandom представляет собой генератор

случайных чисел, который принимает значение с предыдущего шага и строит по нему следующее значение.

Построим тип для случайных чисел:

type Random a = State Double a

next :: Random Double

next = State $ \s -> (s, nextRandom s)

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

1:

addRandom :: Double -> Random Double

addRandom x = fmap (+x) next

Посмотрим как эта функция работает в интерпретаторе:

*Random> runState (addRandom 5) 0.5

(5.5,0.9735000000000014)

*Random> runState (addRandom 5) 0.7

(5.7,0.16289999999999338)

*Random> runState (mapM addRandom [1 .. 5]) 0.5

([1.5,2.9735000000000014,3.139404500000154,4.769488561516319,

5.5250046269694195],0.6226652135290891)

В последней строчке мы с помощью функции mapM прибавили ко всем элементам списка разные случайные

числа, обновление счётчика происходило за кадром, с помощью функции mapM и экземпляра Monad для State.

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

[-1+a, 1+a], а другое из интервала [-2+b,2+b]:

addRandom2 :: Double -> Double -> Random Double

addRandom2 a b = liftA2 add next next

where add

a b = \x y -> diap a 1 x + diap b 1 y

diap c r = \x

-> x * 2 * r - r + c

Функция diap перемещает интервал от 0 до 1 в интервал от c-r до c+r. Обратите внимание на то как мы

сначала составили обычную функцию add, которая перемещает значения из интервала от 0 до 1 в нужный

диапазон и складывает. И только в самый последний момент мы применили к этой функции случайные

значения. Посмотрим как работает эта функция:

*Random> runState (addRandom2 0 10) 0.5

(10.947000000000003,0.13940450000015403)

*Random> runState (addRandom2 0 10) 0.7

(9.725799999999987,0.2587662999992979)

Прибавим два списка и получим сумму:

*Random> let res = fmap sum $ zipWithM addRandom2 [1.. 3] [11 .. 13]

*Random> runState res 0.5

(43.060125804029965,0.969511377766409)

*Random> runState res 0.7

(39.86034841613788,0.26599261421101517)

Функция zipWithM является аналогом функции zipWith. Она устроена также как и функция mapM, сначала

применяется обычная функция zipWith, а затем функция sequence.

С помощью типа Random мы можем определить функцию подбрасывания монетки:

Случайные числа | 107

data Coin = Heads | Tails

deriving (Show)

dropCoin :: Random Coin

dropCoin = fmap drop’ next

where drop’ x

| x < 0.5

= Heads

| otherwise = Tails

У монетки две стороны орёл (Heads) и решка (Tails). Поскольку шансы на выпадание той или иной

стороны равны, мы для определения стороны разделяем интервал от 0 до 1 в равных пропорциях.

Подбросим монетку пять раз:

*Random> let res = sequence $ replicate 5 dropCoin

Функция replicate n a составляет список из n повторяющихся элементов a. Посмотрим что у нас полу-

чилось:

*Random> runState res 0.4

([Heads, Heads, Heads, Heads, Tails],0.5184926967068364)

*Random> runState res 0.5

([Tails, Tails, Heads, Tails, Tails],0.6226652135290891)

7.2 Конечные автоматы

С помощью монады State можно описывать конечные автоматы (finite-state machine). Конечный автомат

находится в каком-то начальном состоянии. Он принимает на вход ленту событий. Одно событие происходит

за другим. На каждое событие автомат реагирует переходом из одного состояния в другое.

type FSM s = State s s

fsm :: (ev -> s -> s) -> (ev -> FSM s)

fsm transition = \e -> State $ \s -> (s, transition e s)

Функция fsm принимает функцию переходов состояний transition и возвращает функцию, которая при-

нимает состояние и возвращает конечный автомат. В качестве значения конечный автомат FSM будет возвра-

щать текущее состояние.

С помощью конечных автоматов можно описывать различные устройства. Лентой событий будет ввод

пользователя (нажатие на кнопки, включение/выключение питания).

Приведём простой пример. Рассмотрим колонки, у них есть розетка, кнопка вкл/выкл и регулятор гром-

кости. Возможные состояния:

type Speaker = (SpeakerState, Level)

data SpeakerState = Sleep | Work

deriving (Show)

data Level

= Level Int

deriving (Show)

Тип колонок складывается из двух значений: состояния и уровня громкости. Колонки могут быть вы-

ключенными (Sleep) или работать на определённой громкости (Work). Считаем, что максимальный уровень

громкости составляет 10 единиц, а минимальный ноль единиц. Границы диапазона громкости описываются

такими функциями:

quieter :: Level -> Level

quieter (Level n) = Level $ max 0 (n-1)

louder :: Level -> Level

louder (Level n) = Level $ min 10 (n+1)

Перейти на страницу:

Похожие книги