между всеми смежными списками, содержащимися в списке.
Законы моноидов действительно выполняются для экземпляра списка. Когда у нас есть несколько списков и мы объединяем их с помощью функции >mappend
(или >++
), не имеет значения, какие списки мы соединяем первыми, поскольку так или иначе они соединяются на концах. Кроме того, пустой список действует как единица, поэтому всё хорошо.
Обратите внимание, что моноиды не требуют, чтобы результат выражения >a `mappend` b
был равен результату выражения >b `mappend` a
. В случае со списками они очевидно не равны:
>ghci> "один" `mappend` "два"
>"одиндва"
>ghci> "два" `mappend` "один"
>"дваодин"
И это нормально. Тот факт, что при умножении выражения >3 * 5
и >5 * 3
дают один и тот же результат, – это просто свойство умножения, но оно не выполняется для большинства моноидов.
Мы уже изучили один из способов рассматривать числа как моноиды: просто позволить бинарной функции быть оператором >*
, а единичному значению – быть >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
. Это значит, что