haskell-notes

установленный предел.

Определим колонки другого типа. Наши новые колонки будут безопаснее предыдущих. Представьте си-

туацию, что мы выключили колонки на высоком уровне громкости. Мы слушали домашнюю запись с низким

уровнем звука. Мы выключили и забыли. Потом мы решили послушать другую мелодию, которая записана

с нормальным уровнем звука. При включении колонок нас оглушил шквал звука. Чтобы этого избежать мы

решили воспользоваться другими колонками.

Колонки при выключении будут выставлять уровень громкости на ноль и реле можно будет крутить

только если колонки включены.

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,

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

значения (Lit), либо переменные (Var). Нам было бы удобно иметь возможность пользоваться окружением

из любого узла дерева. В этом нам поможет тип Reader.

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

по имени:

value :: Env -> String -> Int

Теперь определим функцию eval:

eval :: Exp -> Reader Env Int

eval (Lit n)

= pure n

eval (Neg n)

= liftA

negate $ eval n

eval (Add a b)

= liftA2 (+) (eval a) (eval b)

eval (Mul a b)

= liftA2 (*) (eval a) (eval b)

eval (Var name) = Reader $ env -> value env name

Определение сильно изменилось, оно стало не таким наглядным. Теперь значение eval стало специаль-

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

обычные функции вычитания, сложения и умножения. Мы можем записать это выражение

немного по другому:

eval :: Exp -> Reader Env Int

eval (Lit n)

= pure n

eval (Neg n)

= negateA $ eval n

eval (Add a b)

= eval a ‘addA‘ eval b

eval (Mul a b)

= eval a ‘mulA‘ eval b

eval (Var name) = Reader $ env -> value env name

addA

= liftA2 (+)

mulA

= liftA2 (*)

negateA

= liftA negate

Тип Map

Для того чтобы закончить определение функции eval нам нужно определить тип Env и функцию value.

Для этого мы воспользуемся типом Map, он предназначен для хранения значений по ключу.

Этот тип живёт в стандартном модуле Data.Map. Посмотрим на его описание:

data Map k a = ..

Первый параметр типа k это ключ, а второй это значение. Мы можем создать значение типа Map из списка

пар ключ значение с помощью функции fromList.

Посмотрим на основные функции:

— Создаём значения типа Map

— создаём

empty :: Map k a

— пустой Map

fromList :: Ord k => [(k, a)] -> Map k a

— по списку (ключ, значение)

— Узнаём значение по ключу

(! )

:: Ord k => Map k a -> k -> a

Отложенное вычисление выражений | 111

lookup

:: Ord k => k -> Map k a -> Maybe a

— Добавляем элементы

insert :: Ord k => k -> a -> Map k a -> Map k a

— Удаляем элементы

delete :: Ord k => k -> Map k a -> Map k a

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

Посмотрим как эти функции работают:

*Exp> :m +Data.Map

*Exp Data.Map> :m -Exp

Data.Map> let v = fromList [(1, ”Hello”), (2, ”Bye”)]

Data.Map> v ! 1

”Hello”

Data.Map> v ! 3

”*** Exception: Map.find: element not in the map

Data.Map> lookup 3 v

Nothing

Data.Map> let v1 = insert 3 ” Yo” v

Data.Map> v1 ! 3

Yo

Функция lookup является стабильным аналогом функции ! . В том смысле, что она определена с помощью

Maybe. Она не приведёт к падению программы, если для данного ключа не найдётся значение.

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