Типы Product и Sum
Мы уже изучили один из способов рассматривать числа как моноиды: просто позволить бинарной функции быть оператором *, а единичному значению – быть 1. Ещё один способ для чисел быть моноидами состоит в том, чтобы в качестве бинарной функции выступал оператор +, а в качестве единичного значения – значение 0:
ghci> 0 + 4
4
ghci> 5 + 0
5
ghci> (1 + 3) + 5
9
ghci> 1 + (3 + 5)
9
Законы моноидов выполняются, потому что если вы прибавите 0 к любому числу, результатом будет то же самое число. Сложение также ассоциативно, поэтому здесь у нас нет никаких проблем.
Итак, в нашем распоряжении два одинаково правомерных способа для чисел быть моноидами. Какой же способ выбрать?.. Ладно, мы не обязаны выбирать! Вспомните, что когда имеется несколько способов определения для какого-то типа экземпляра одного и того же класса типов, мы можем обернуть этот тип в декларацию newtype, а затем сделать для нового типа экземпляр класса типов по-другому. Можно совместить несовместимое.
Модуль Data.Monoid экспортирует для этого два типа: Product и Sum.
Product определён вот так:
newtype Product a = Product { getProduct :: a }
deriving (Eq, Ord, Read, Show, Bounded)
Это всего лишь обёртка newtype с одним параметром типа наряду с некоторыми порождёнными экземплярами. Его экземпляр для класса Monoid выглядит примерно так:
instance Num a => Monoid (Product a) where
mempty = Product 1
Product x `mappend` Product y = Product (x * y)
Значение mempty – это просто 1, обёрнутая в конструктор Product. Функция mappend производит сопоставление конструктора Product с образцом, перемножает два числа, а затем оборачивает результирующее число. Как вы можете видеть, имеется ограничение класса Num a. Это значит, что Product a является экземпляром Monoid для всех значений типа a, для которых уже имеется экземпляр класса Num. Для того чтобы использовать тип Product a в качестве моноида, мы должны произвести некоторое оборачивание и разворачивание newtype:
ghci> getProduct $ Product 3 `mappend` Product 9
27
ghci> getProduct $ Product 3 `mappend` mempty
3
ghci> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2
24
ghci> getProduct . mconcat . map Product $ [3,4,2]
24
Тип Sum определён в том же духе, что и тип Product, и экземпляр тоже похож. Мы используем его точно так же:
ghci> getSum $ Sum 2 `mappend` Sum 9
11
ghci> getSum $ mempty `mappend` Sum 3
3
ghci> getSum . mconcat . map Sum $ [1,2,3]
6