Так же, как при использовании умножения и сложения, мы обычно явно указываем бинарные функции вместо оборачивания их в значения newtype
mappend
и mempty
. Функция mconcat
кажется полезной для типов Any
и All
, но обычно проще использовать функции or
и and
. Функция or
принимает списки значений типа Bool
и возвращает True
, если какое-либо из них равно True
. Функция and
принимает те же значения и возвращает значение True
, если все из них равны True
.Моноид Ordering
Помните тип Ordering
LT
, EQ
и GT
, которые соответственно означают «меньше, чем», «равно» и «больше, чем».ghci> 1 `compare` 2
LT
ghci> 2 `compare` 2
EQ
ghci> 3 `compare` 2
GT
При использовании чисел и значений типа Bool
Ordering
нам придётся приложить больше старания, чтобы распознать моноид. Оказывается, его экземпляр класса Monoid
настолько же интуитивен, насколько и предыдущие, которые мы уже встречали, и кроме того, весьма полезен:instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT
Экземпляр определяется следующим образом: когда мы объединяем два значения типа Ordering
mappend
, сохраняется значение слева, если значение слева не равно EQ
. Если значение слева равно EQ
, результатом будет значение справа. Единичным значением является EQ
. На первый взгляд, такой выбор может показаться несколько случайным, но на самом деле он имеет сходство с тем, как мы сравниваем слова в алфавитном порядке. Мы смотрим на первые две буквы, и, если они отличаются, уже можем решить, какое слово шло бы первым в словаре. Если же первые буквы равны, то мы переходим к сравнению следующей пары букв, повторяя процесс[13].Например, сравнивая слова «
EQ
является единичным значением, обратите внимание, что если бы мы втиснули одну и ту же букву в одну и ту же позицию в обоих словах, их расположение друг относительно друга в алфавитном порядке осталось бы неизменным; к примеру, слово «Важно, что в экземпляре класса Monoid
Ordering
выражение x `mappend` y
не равно выражению y `mappend` x
. Поскольку первый параметр сохраняется, если он не равен EQ
, LT `mappend` GT
в результате вернёт LT
, тогда как GT `mappend` LT
в результате вернёт GT
:ghci> LT `mappend` GT
LT
ghci> GT `mappend` LT
GT
ghci> mempty `mappend` LT
LT
ghci> mempty `mappend` GT
GT
Хорошо, так чем же этот моноид полезен? Предположим, мы пишем функцию, которая принимает две строки, сравнивает их длину и возвращает значение типа Ordering
EQ
, мы хотим установить их расположение в алфавитном порядке.Вот один из способов это записать:
lengthCompare :: String –> String –> Ordering
lengthCompare x y = let a = length x `compare` length y
b = x `compare` y
in if a == EQ then b else a
Результат сравнения длин мы присваиваем образцу a
b
; затем, если оказывается, что длины равны, возвращаем их порядок по алфавиту.Но, имея представление о том, что тип Ordering
import Data.Monoid
lengthCompare :: String –> String –> Ordering
lengthCompare x y = (length x `compare` length y) `mappend`(x `compare` y)
Давайте это опробуем:
ghci> lengthCompare "ямб" "хорей"
LT
ghci> lengthCompare "ямб" "хор"
GT