Теперь, когда вы знаете, что такое моноиды, давайте изучим некоторые типы в языке Haskell, которые являются моноидами, посмотрим, как выглядят экземпляры класса >Monoid
для них, и поговорим об их использовании.
Списки являются моноидами
Да, списки являются моноидами! Как вы уже видели, функция >++
с пустым списком >[]
образуют моноид. Экземпляр очень прост:
>instance Monoid [a] where
> mempty = []
> mappend = (++)
Для списков имеется экземпляр класса >Monoid
независимо от типа элементов, которые они содержат. Обратите внимание, что мы написали >instance Monoid [a]
, а не >instance Monoid []
, поскольку класс >Monoid
требует конкретный тип для экземпляра.
При тестировании мы не встречаем сюрпризов:
>ghci> [1,2,3] `mappend` [4,5,6]
>[1,2,3,4,5,6]
>ghci> ("один" `mappend` "два") `mappend` "три"
>"одиндватри"
>ghci> "один" `mappend` ("два" `mappend` "три")
>"одиндватри"
>ghci> "один" `mappend` "два" `mappend` "три"
>"одиндватри"
>ghci> "бах" `mappend` mempty
>"бах"
>ghci> mconcat [[1,2],[3,6],[9]]
>[1,2,3,6,9]
>ghci> mempty :: [a]
>[]
Обратите внимание, что в последней строке мы написали явную аннотацию типа. Если бы было написано просто >mempty
, то интерпретатор GHCi не знал бы, какой экземпляр использовать, поэтому мы должны были сказать, что нам нужен списковый экземпляр. Мы могли использовать общий тип >[a]
(в отличие от указания >[Int]
или >[String]
), потому что пустой список может действовать так, будто он содержит любой тип.
Поскольку функция >mconcat
имеет реализацию по умолчанию, мы получаем её просто так, когда определяем экземпляр класса >Monoid
для какого-либо типа. В случае со списком функция >mconcat
соответствует просто функции >concat
. Она принимает список списков и «разглаживает» его, потому что это равнозначно вызову оператора