Изучай Haskell во имя добра! (Липовача) - страница 207

, мы полагаемся на то, что его экземпляры ведут себя как моноиды. Иначе какой в этом смысл? Именно поэтому при создании экземпляров класса >Monoid мы должны убедиться, что они следуют нижеприведённым законам:

• >mempty>`mappend`>x>=>x

• >x>`mappend`>mempty>=>x

• >(x>`mappend`>y)>`mappend`>z>=>x>`mappend`>(y>`mappend`>z)

Первые два закона утверждают, что значение >mempty должно вести себя как единица по отношению к функции >mappend, а третий говорит, что функция >mappend должна быть ассоциативна (порядок, в котором мы используем функцию >mappend для сведения нескольких моноидных значений в одно, не имеет значения). Язык Haskell не проверяет определяемые экземпляры на соответствие этим законам, поэтому мы должны быть внимательными, чтобы наши экземпляры действительно выполняли их.

Познакомьтесь с некоторыми моноидами

Теперь, когда вы знаете, что такое моноиды, давайте изучим некоторые типы в языке 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. Она принимает список списков и «разглаживает» его, потому что это равнозначно вызову оператора