элемент пары с помощью snd, мы выделяем остаток. Функция nextRandom представляет собой генератор
случайных чисел, который принимает значение с предыдущего шага и строит по нему следующее значение.
Построим тип для случайных чисел:
type Random
a = State Double anext :: 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 nextwhere
adda b =
\x y -> diap a 1 x + diap b 1 ydiap 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’ nextwhere
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 sfsm ::
(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)