haskell-notes

*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 $ trans

where 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).

Такие типы называют абстрактными синтаксическими деревьями (abstract syntax tree, AST). Они содержат

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

в значении типа Exp. Сделаем экземпляр для Num:

instance Num Exp where

negate

= Neg

(+)

= Add

(*)

= Mul

fromInteger = Lit . fromInteger

abs

= undefined

signum

= undefined

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

var :: String -> Exp

var = Var

n :: Int -> Exp

n = var . show

Функция var составляет переменную с данным именем, а функция n составляет переменную, у которой

имя является целым числом. Сохраним эти определения в модуле Exp. Теперь у нас всё готово для составле-

ния выражений:

*Exp> n 1

Var ”1”

*Exp> n 1 + 2

Add (Var ”1”) (Lit 2)

*Exp> 3 * (n 1 + 2)

Mul (Lit 3) (Add (Var ”1”) (Lit 2))

*Exp> — n 2 * 3 * (n 1 + 2)

Neg (Mul (Mul (Var ”2”) (Lit 3)) (Add (Var ”1”) (Lit 2)))

110 | Глава 7: Функторы и монады: примеры

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

и возвращать целое число.

eval :: Exp -> Int

eval (Lit n)

= n

eval (Neg n)

= negate $ eval n

eval (Add a b)

= eval a + eval b

eval (Mul a b)

= eval a * eval b

eval (Var name) = ???

Как быть с конструктором Var? Нам нужно откуда-то узнать какое значение связано с переменной. Функ-

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

Этот набор значений мы будем называть окружением.

Обратите внимание на то, что в каждом составном конструкторе мы рекурсивно вызываем функцию eval,

мы словно обходим всё дерево выражения. Спускаемся вниз, до самых листьев в которых расположены либо

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162