Вспомните, что когда мы используем функцию mappend
, сохраняется её левый параметр, если он не равен значению EQ
; если он равен EQ
, сохраняется правый. Вот почему мы поместили сравнение, которое мы считаем первым, более важным критерием, в качестве первого параметра. Теперь предположим, что мы хотим расширить эту функцию, чтобы она также сравнивала количество гласных звуков, и установить это вторым по важности критерием для сравнения. Мы изменяем её вот так:
import Data.Monoid
lengthCompare :: String –> String –> Ordering
lengthCompare x y = (length x `compare` length y) `mappend`
(vowels x `compare` vowels y) `mappend`
(x `compare` y)
where vowels = length . filter (`elem` "аеёиоуыэюя")
Мы создали вспомогательную функцию, которая принимает строку и сообщает нам, сколько она содержит гласных звуков, сначала отфильтровывая в ней только буквы, находящиеся в строке "аеёиоуыэюя"
, а затем применяя функцию length
.
ghci> lengthCompare "ямб" "абыр"
LT
ghci> lengthCompare "ямб" "абы"
LT
ghci> lengthCompare "ямб" "абр"
GT
В первом примере длины оказались различными, поэтому вернулось LT
, так как длина слова "ямб"
меньше длины слова "абыр"
. Во втором примере длины равны, но вторая строка содержит больше гласных звуков, поэтому опять возвращается LT
. В третьем примере они обе имеют одинаковую длину и одинаковое количество гласных звуков, поэтому сравниваются по алфавиту, и слово "ямб"
выигрывает.
Моноид для типа Ordering
очень полезен, поскольку позволяет нам без труда сравнивать сущности по большому количеству разных критериев и помещать сами эти критерии по порядку, начиная с наиболее важных и заканчивая наименее важными.
Рассмотрим несколько способов, которыми для типа Maybe a
могут быть определены экземпляры класса Monoid
, и обсудим, чем эти экземпляры полезны.
Один из способов состоит в том, чтобы обрабатывать тип Maybe a
как моноид, только если его параметр типа a
тоже является моноидом, а потом реализовать функцию mappend
так, чтобы она использовала операцию mappend
для значений, обёрнутых в конструктор Just
. Мы используем значение Nothing
как единичное, и поэтому если одно из двух значений, которые мы объединяем с помощью функции mappend
, равно Nothing
, мы оставляем другое значение. Вот объявление экземпляра:
instance Monoid a => Monoid (Maybe a) where
mempty = Nothing
Nothing `mappend` m = m
m `mappend` Nothing = m
Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
Обратите внимание на ограничение класса. Оно говорит, что тип Maybe
является моноидом, только если для типа a
определён экземпляр класса Monoid
. Если мы объединяем нечто со значением Nothing
, используя функцию mappend
, результатом является это нечто. Если мы объединяем два значения Just
с помощью функции mappend
, то содержимое значений Just
объединяется с помощью этой функции, а затем оборачивается обратно в конструктор Just
. Мы можем делать это, поскольку ограничение класса гарантирует, что тип значения, которое находится внутри Just
, имеет экземпляр класса Monoid
.
ghci> Nothing `mappend` Just "андрей"
Just "андрей"
ghci> Just LT `mappend` Nothing
Just LT
ghci> Just (Sum 3) `mappend` Just (Sum 4)
Just (Sum {getSum = 7})
Это полезно, когда мы имеем дело с моноидами как с результатами вычислений, которые могли окончиться неуспешно. Из-за наличия этого экземпляра нам не нужно проверять, окончились ли вычисления неуспешно, определяя, вернули они значение Nothing
или Just
; мы можем просто продолжить обрабатывать их как обычные моноиды.
Но что если тип содержимого типа Maybe
не имеет экземпляра класса Monoid
? Обратите внимание: в предыдущем объявлении экземпляра единственный случай, когда мы должны полагаться на то, что содержимые являются моноидами, – это когда оба параметра функции mappend
обёрнуты в конструктор Just
. Когда мы не знаем, являются ли содержимые моноидами, мы не можем использовать функцию mappend
между ними; так что же нам делать? Ну, единственное, что мы можем сделать, – это отвергнуть второе значение и оставить первое. Для этой цели существует тип First
a
. Вот его определение:
newtype First a = First { getFirst :: Maybe a }
deriving (Eq, Ord, Read, Show)