Изучай Haskell во имя добра! (Липовача) - страница 246

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

>import Data.Monoid


>type Food = String

>type Price = Sum Int


>addDrink :: Food –> (Food,Price)

>addDrink "бобы" = ("молоко", Sum 25)

>addDrink "вяленое мясо" = ("виски", Sum 99)

>addDrink _ = ("пиво", Sum 30)

Мы используем строки для представления продуктов и тип >Int в обёртке типа >newtype Sum для отслеживания того, сколько центов стоит тот или иной продукт. Просто напомню: выполнение функции >mappend для значений типа >Sum возвращает сумму обёрнутых значений.

>ghci> Sum 3 `mappend` Sum 9

>Sum {getSum = 12}

Функция >addDrink довольно проста. Если мы едим бобы, она возвращает >"молоко" вместе с >Sum>25; таким образом, >25 центов завёрнуты в конструктор >Sum. Если мы едим вяленое мясо, то пьём виски, а если едим что-то другое – пьём пиво. Обычное применение этой функции к продукту сейчас было бы не слишком интересно, а вот использование функции >applyLog для передачи продукта с указанием цены в саму функцию представляет интерес:

>ghci> ("бобы", Sum 10) `applyLog` addDrink

>("молоко",Sum {getSum = 35})

>ghci> ("вяленое мясо", Sum 25) `applyLog` addDrink

>("виски",Sum {getSum = 124})

>ghci> ("собачатина", Sum 5) `applyLog` addDrink

>("пиво",Sum {getSum = 35})

Молоко стоит 25 центов, но если мы заедаем его бобами за 10 центов, это обходится нам в 35 центов. Теперь ясно, почему присоединённое значение не всегда должно быть журналом – оно может быть любым моноидным значением, и то, как эти два значения объединяются, зависит от моноида. Когда мы производили записи в журнал, они присоединялись в конец, но теперь происходит сложение чисел.

Поскольку значение, возвращаемое функцией >addDrink, является кортежем типа >(Food,Price), мы можем передать этот результат функции >addDrink ещё раз, чтобы функция сообщила нам, какой напиток будет подан в сопровождение к блюду и сколько это нам будет стоить. Давайте попробуем:

>ghci> ("собачатина", Sum 5) `applyLog` addDrink `applyLog` addDrink

>("пиво",Sum {getSum = 65})

Добавление напитка к какой-нибудь там собачатине вернёт пиво и дополнительные 30 центов, то есть >("пиво", Sum 35). А если мы используем функцию >applyLog для передачи этого результата функции >addDrink, то получим ещё одно пиво, и результатом будет >("пиво", Sum 65).

Тип Writer

Теперь, когда мы увидели, что значение с присоединённым моноидом ведёт себя как монадическое значение, давайте исследуем экземпляр класса >Monad для типов таких значений. Модуль >Control.Monad.Writer экспортирует тип