eval ( Adda b)
=liftA2 ( +) (eval a) (eval b)
eval ( Mula b)
=liftA2 ( *) (eval a) (eval b)
eval ( Varname) = Reader $\env ->value env name
Определение сильно изменилось, оно стало не таким наглядным. Теперь значение eval стало специаль-
ным, поэтому при рекурсивном вызове функции eval нам приходится поднимать в мир специальных функций
обычные функции вычитания, сложения и умножения. Мы можем записать это выражение
немного по другому:
eval :: Exp -> Reader Env Int
eval ( Litn)
=pure n
eval ( Negn)
=negateA $eval n
eval ( Adda b)
=eval a ‘addA‘ eval b
eval ( Mula b)
=eval a ‘mulA‘ eval b
eval ( Varname) = Reader $\env ->value env name
addA
=liftA2 ( +)
mulA
=liftA2 ( *)
negateA
=liftA negate
Тип Map
Для того чтобы закончить определение функции eval нам нужно определить тип Envи функцию value.
Для этого мы воспользуемся типом Map, он предназначен для хранения значений по ключу.
Этот тип живёт в стандартном модуле Data.Map. Посмотрим на его описание:
data Mapk a = ..
Первый параметр типа k это ключ, а второй это значение. Мы можем создать значение типа Mapиз списка
пар ключ значение с помощью функции fromList.
Посмотрим на основные функции:
-- Создаём значения типа Map
-- создаём
empty :: Mapk a
-- пустой Map
fromList :: Ordk =>[(k, a)] -> Mapk a
-- по списку (ключ, значение)
-- Узнаём значение по ключу
( !)
:: Ordk => Mapk a ->k ->a
Отложенное вычисление выражений | 111
lookup
:: Ordk =>k -> Mapk a -> Maybea
-- Добавляем элементы
insert :: Ordk =>k ->a -> Mapk a -> Mapk a
-- Удаляем элементы
delete :: Ordk =>k -> Mapk a -> Mapk a
Обратите внимание на ограничение Ordk в этих функциях, ключ должен быть экземпляром класса Ord.
Посмотрим как эти функции работают:
*Exp> :m +Data.Map
*Exp Data.Map> :m -Exp
Data.Map> letv =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. Она не приведёт к падению программы, если для данного ключа не найдётся значение.
Теперь мы можем определить функцию value:
import qualified Data.Map asM( Map, lookup, fromList)
...
type Env = M.Map String Int
value :: Env -> String -> Int
value env name =maybe errorMsg $ M.lookup env name
whereerrorMsg = error $”value is undefined for ” ++name
Обычно функции из модуля Data.Mapвключаются с директивой qualified, поскольку имена многих
функций из этого модуля совпадают с именами из модуля Prelude. Теперь все определения из модуля
Data.Mapпишутся с приставкой M..
Создадим вспомогательную функцию, которая упростит вычисление выражений:
runExp :: Exp ->[( String, Int)] -> Int
runExp a env =runReader (eval a) $ M.fromList env
Сохраним определение новых функций в модуле Exp. И посмотрим что у нас получилось:
*Exp> letenv a b =[(”1”, a), (”2”, b)]
*Exp> letexp =2 *(n 1 +n 2) -n 1
*Exp>runExp exp (env 1 2)
5
*Exp>runExp exp (env 10 5)
20
Так мы можем пользоваться функциями с окружением для того, чтобы читать значения из общего ис-
точника. Впрочем мы можем просто передавать окружение дополнительным аргументом и не пользоваться
монадами:
eval :: Env -> Exp -> Int
eval env x = casex of
Litn
->n
Negn
->negate $eval’ n
Adda b
->eval’ a +eval’ b
Mula b
->eval’ a +eval’ b
Varname
->value env name
whereeval’ =eval env
112 | Глава 7: Функторы и монады: примеры
7.4 Накопление результата
Рассмотрим по-подробнее тип Writer. Он выполняет задачу обратную к типу Reader. Когда мы пользова-
лись типом Reader, мы могли в любом месте функции извлекать данные из окружения. Теперь же мы будем
Читать дальше