Мы берём тип Maybe a
и оборачиваем его с помощью декларации newtype
. Экземпляр класса Monoid
в данном случае выглядит следующим образом:
instance Monoid (Firsta) where
mempty = First Nothing
First (Just x) `mappend` _ = First (Just x)
First Nothing `mappend` x = x
Значение mempty
– это просто Nothing
, обёрнутое с помощью конструктора First
. Если первый параметр функции mappend
является значением Just
, мы игнорируем второй. Если первый параметр – Nothing
, тогда мы возвращаем второй параметр в качестве результата независимо от того, является ли он Just
или Nothing
:
ghci> getFirst $ First (Just 'a') `mappend` First (Just 'b')
Just 'a'
ghci> getFirst $ First Nothing `mappend` First (Just 'b')
Just 'b'
ghci> getFirst $ First (Just 'a') `mappend` First Nothing
Just 'a'
Тип First
полезен, когда у нас есть множество значений типа Maybe
и мы хотим знать, является ли какое-либо из них значением Just
. Для этого годится функция mconcat
:
ghci> getFirst . mconcat . map First $ [Nothing, Just 9, Just 10]
Just 9
Если нам нужен моноид на значениях Maybe a
– такой, чтобы оставался второй параметр, когда оба параметра функции mappend
являются значениями Just
, то модуль Data.Monoid
предоставляет тип Last a
, который работает, как и тип First a
, но при объединении с помощью функции mappend
и использовании функции mconcat
сохраняется последнее значение, не являющееся Nothing
:
ghci> getLast . mconcat . map Last $ [Nothing, Just 9, Just 10]
Just 10
ghci> getLast $ Last (Just "один") `mappend` Last (Just "два")
Just "two"
Свёртка на моноидах
Один из интересных способов ввести моноиды в работу заключается в том, чтобы они помогали нам определять свёртки над различными структурами данных. До сих пор мы производили свёртки только над списками, но списки – не единственная структура данных, которую можно свернуть. Мы можем определять свёртки почти над любой структурой данных. Особенно хорошо поддаются свёртке деревья.
Поскольку существует так много структур данных, которые хорошо работают со свёртками, был введён класс типов Foldable
. Подобно тому как класс Functor
предназначен для сущностей, которые можно отображать, класс Foldable
предназначен для вещей, которые могут быть свёрнуты! Его можно найти в модуле Data.Foldable
; и, поскольку он экспортирует функции, имена которых конфликтуют с именами функций из модуля Prelude
, его лучше импортировать, квалифицируя (и подавать с базиликом!):
import qualified Data.Foldable as F
Чтобы сэкономить драгоценные нажатия клавиш, мы импортировали его, квалифицируя как F
.
Так какие из некоторых функций определяет этот класс типов? Среди них есть функции foldr
, foldl
, foldr1
и foldl1
. Ну и?.. Мы уже давно знакомы с ними! Что ж в этом нового? Давайте сравним типы функции foldr
из модуля Foldable
и одноимённой функции из модуля Prelude
, чтобы узнать, чем они отличаются:
ghci> :t foldr
foldr :: (a –> b –> b) –> b –> [a] –> b
ghci> :t F.foldr
F.foldr :: (F.Foldable t) => (a –> b –> b) –> b –> t a –> b
А-а-а! Значит, в то время как функция foldr
принимает список и сворачивает его, функция foldr
из модуля Data.Foldable
принимает любой тип, который можно свернуть, – не только списки! Как и ожидалось, обе функции foldr
делают со списками одно и то же:
ghci> foldr (*) 1 [1,2,3]
6
ghci> F.foldr (*) 1 [1,2,3]
6
Другой структурой данных, поддерживающей свёртку, является Maybe
, которую мы все знаем и любим!
ghci> F.foldl (+) 2 (Just 9)
11
ghci> F.foldr (||) False (Just True)
True
Но сворачивание значения Maybe
не очень-то интересно. Оно действует просто как список с одним элементом, если это значение Just
, и как пустой список, если это значение Nothing
. Давайте рассмотрим чуть более сложную структуру данных.
Помните древовидную структуру данных из главы 7? Мы определили её так:
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)