52700.fb2
точника. Впрочем мы можем просто передавать окружение дополнительным аргументом и не пользоваться
монадами:
eval :: Env -> Exp -> Int
eval env x = case x of
Lit n
-> n
Neg n
-> negate $ eval’ n
Add a b
-> eval’ a + eval’ b
Mul a b
-> eval’ a + eval’ b
Var name
-> value env name
where eval’ = eval env
112 | Глава 7: Функторы и монады: примеры
7.4 Накопление результата
Рассмотрим по-подробнее тип Writer. Он выполняет задачу обратную к типу Reader. Когда мы пользова-
лись типом Reader, мы могли в любом месте функции извлекать данные из окружения. Теперь же мы будем
не извлекать данные из окружения, а записывать их.
Рассмотрим такую задачу нам нужно обойти дерево типа Exp и подсчитать все бинарные операции. Мы
прибавляем к накопителю результата единицу за каждый конструктор Add или Mul. Тип сообщений будет
числом. Нам нужно сделать экземпляр класса Monoid для чисел.
Напомню, что тип накопителя должен быть экземпляром класса Monoid:
class Monoid a where
mempty
:: a
mappend :: a -> a -> a
mconcat :: [a] -> a
mconcat = foldr mappend mempty
Но для чисел возможно несколько вариантов, которые удовлетворяют свойствам. Для сложения:
instance Num a => Monoid a where
mempty
= 0
mappend = (+)
И умножения:
instance Num a => Monoid a where
mempty
= 1
mappend = (*)
Для нашей задачи подойдёт первый вариант, но не исключена возможность того, что для другой зада-
чи нам понадобится второй. Но тогда мы уже не сможем определить такой экземпляр. Для решения этой
проблемы в модуле Data.Monoid определено два типа обёртки:
newtype Sum
a = Sum
{ getSum
:: a }
newtype Prod a = Prod { getProd :: a }
В этом определении есть два новых элемента. Первый это ключевое слово newtype, а второй это фигурные