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
Типы Any и All
Ещё одним типом, который может действовать как моноид двумя разными, но одинаково допустимыми способами, является Bool
. Первый способ состоит в том, чтобы заставить функцию ||
, которая представляет собой логическое ИЛИ, действовать как бинарная функция, используя False
в качестве единичного значения. Если при использовании логического ИЛИ какой-либо из параметров равен True
, функция возвращает True
; в противном случае она возвращает False
. Поэтому если мы используем False
в качестве единичного значения, операция ИЛИ вернёт False
при использовании с False
– и True
при использовании с True
. Конструктор newtype Any
аналогичным образом имеет экземпляр класса Monoid
. Он определён вот так:
newtype Any = Any { getAny :: Bool }
deriving (Eq, Ord, Read, Show, Bounded)
А его экземпляр выглядит так:
instance Monoid Any where
mempty = Any False
Any x `mappend` Any y = Any (x || y)
Он называется Any
, потому что x `mappend` y
будет равно True
, если True
. Даже когда три или более значений Bool
, обёрнутых в Any
, объединяются с помощью функции mappend
, результат будет содержать True
, если любое из них равно True
.
ghci> getAny $ Any True `mappend` Any False
True
ghci> getAny $ mempty `mappend` Any True
True
ghci> getAny . mconcat . map Any $ [False, False, False, True]
True
ghci> getAny $ mempty `mappend` mempty
False
Другой возможный вариант экземпляра класса Monoid
для типа Bool
– всё как бы наоборот: заставить оператор &&
быть бинарной функцией, а затем сделать значение True
единичным значением. Логическое И вернёт True
, только если оба его параметра равны True
.
Это объявление newtype
:
newtype All = All { getAll :: Bool }
deriving (Eq, Ord, Read, Show, Bounded)
А это экземпляр:
instance Monoid All where
mempty = All True
All x `mappend` All y = All (x && y)
Когда мы объединяем значения типа All
с помощью функции mappend
, результатом будет True
только в случае, если все значения, использованные в функции mappend
, равны True
:
ghci> getAll $ mempty `mappend` All True
True
ghci> getAll $ mempty `mappend` All False
False
ghci> getAll . mconcat . map All $ [True, True, True]
True
ghci> getAll . mconcat . map All $ [True, True, False]
False