Мы будем обновлять значения уровня громкости не напрямую, а с помощью вспомогательных функций
louder и quieter. Так мы не сможем выйти за пределы заданного диапазона.
Возможные события:
108 | Глава 7: Функторы и монады: примеры
data User = Button | Quieter | Louder
deriving
(Show)Пользователь может либо нажать на кнопку вкл/выкл или повернуть реле громкости влево, чтобы при-
глушить звук (Quieter
) или вправо, чтобы сделать погромче (Louder). Будем считать, что колонки всегдавключены в розетку.
Составим функцию переходов:
speaker :: User -> FSM Speaker
speaker =
fsm $ transwhere
trans Button(Sleep
, n) = (Work, n)trans Button
(Work
,n) =
(Sleep, n)trans Louder
(s,
n) =
(s, louder n)trans Quieter
(s,
n) =
(s, quieter n)Мы считаем, что при выключении колонок реле остаётся некотором положении, так что при следующем
включении они будут работать на той же громкости. Реле можно крутить и в состоянии Sleep
. Посмотримна типичную сессию работы колонок:
*FSM> let
res = mapM speaker [Button, Louder, Quieter, Quieter, Button]Сначала мы включаем колонки, затем прибавляем громкость, затем дважды делаем тише и в конце вы-
ключаем. Посмотрим что получилось:
*FSM>
runState res (Sleep, Level 2)([(Sleep
, Level 2),(Work, Level 2),(Work, Level 3),(Work, Level 2),(Work
, Level 1)],(Sleep, Level 1))*FSM>
runState res (Sleep, Level 0)([(Sleep
, Level 0),(Work, Level 0),(Work, Level 1),(Work, Level 0),(Work
, Level 0)],(Sleep, Level 0))Смотрите, изменив начальное значение, мы изменили весь список значений. Обратите внимание на то,
что во втором прогоне мы не ушли в минус по громкости, не смотря на то, что пытались крутить реле за
установленный предел.
Определим колонки другого типа. Наши новые колонки будут безопаснее предыдущих. Представьте си-
туацию, что мы выключили колонки на высоком уровне громкости. Мы слушали домашнюю запись с низким
уровнем звука. Мы выключили и забыли. Потом мы решили послушать другую мелодию, которая записана
с нормальным уровнем звука. При включении колонок нас оглушил шквал звука. Чтобы этого избежать мы
решили воспользоваться другими колонками.
Колонки при выключении будут выставлять уровень громкости на ноль и реле можно будет крутить
только если колонки включены.
safeSpeaker :: User -> FSM Speaker
safeSpeaker =
fsm $ transwhere
trans Button(Sleep
, _) = (Work,Level
0)trans Button
(Work
,_
) = (Sleep, Level 0)trans Quieter
(Work,n) =
(Work,quieter n)
trans Louder
(Work
,n) =
(Work,louder n)
trans _
(Sleep
, n) = (Sleep, n)При нажатии на кнопку вкл/выкл уровень громкости выводится в положение 0. Колонки реагируют на
запросы изменения уровня громкости только в состоянии Work
. Посмотрим как работают наши новые колон-ки:
*FSM> let
res = mapM safeSpeaker [Button, Louder, Quieter, Button, Louder]Мы включаем колонки, делаем по-громче, затем по-тише, затем выключаем и пытаемся изменить гром-
кость после выключения. Посмотрим как они сработают, представим, что мы выключили колонки на уровне
громкости 10:
*FSM>
runState res (Sleep, Level 10)([(Sleep
, Level 10),(Work, Level 0),(Work, Level 1),(Work, Level 0),(Sleep
, Level 0)],(Sleep, Level 0))Конечные автоматы | 109
Первое значение в списке является стартовым состоянием, которое мы задали. После этого колонки вклю-
чаются и мы видим, что уровень громкости переключился на ноль. Затем мы увеличиваем громкость, сбав-
ляем её и выключаем. Попытка изменить громкость выключенных колонок не проходит. Это видно по по-
следнему элементу списка и итоговому состоянию колонок, которое находится во втором элементе пары.
Предположим, что колонки работают с самого начала, тогда первым действием мы выключаем их. По-
смотрим, что случится дальше:
*FSM>
runState res (Work, Level 10)([(Work
, Level 10),(Sleep, Level 0),(Sleep, Level 0),(Sleep, Level 0),(Work
, Level 0)],(Work, Level 1))Дальше мы пытаемся изменить громкость но у нас ничего не выходит.
7.3 Отложенное вычисление выражений
В этом примере мы будем выполнять арифметические операции на целых числах. Мы будем их скла-
дывать, вычитать и умножать. Но вместо того, чтобы сразу вычислять выражения мы будем составлять их
описание. Мы будем кодировать операции конструкторами.
data Exp
= Var String
| Lit Int
| Neg Exp
| Add Exp Exp
| Mul Exp Exp
deriving
(Show, Eq)У нас есть тип Exp
, который может быть либо переменной Var с данным строчным именем, либо целочис-ленной константой Lit
, либо одной из трёх операций: вычитанием (Neg), сложением (Add) или умножением(Mul
).