Типы 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