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

.

Если вы немного смущены этим доказательством, не волнуйтесь. Убедитесь, что вы понимаете, как устроена композиция функций. Часто вы можете интуитивно понимать, как выполняются эти законы, поскольку типы действуют как контейнеры или функции. Вы также можете просто проверить их на нескольких разных значениях типа – и сумеете с определённой долей уверенности сказать, что тип действительно подчиняется этим законам.

Нарушение закона

Давайте посмотрим на «патологический» пример конструктора типов, который является экземпляром класса типов >Functor, но не является функтором, потому что он не выполняет законы. Скажем, у нас есть следующий тип:

>data CMaybe a = CNothing | CJust Int a deriving (Show)

Буква >C здесь обозначает счётчик. Это тип данных, который во многом похож на тип >Maybe a, только часть >Just содержит два поля вместо одного. Первое поле в конструкторе данных >CJust всегда имеет тип >Int; оно будет своего рода счётчиком. Второе поле имеет тип >a, который берётся из параметра типа, и его тип будет зависеть от конкретного типа, который мы выберем для >CMaybe a. Давайте поэкспериментируем с нашим новым типом:

>ghci> CNothing

>CNothing

>ghci> CJust 0 "ха-ха"

>CJust 0 "ха-ха"

>ghci> :t CNothing

>CNothing :: CMaybe a

>ghci> :t CJust 0 "ха-ха"

>CJust 0 "ха-ха" :: CMaybe [Char]

>ghci> CJust 100 [1,2,3]

>CJust 100 [1,2,3]

Если мы используем конструктор данных >CNothing, в нём нет полей. Если мы используем конструктор данных >CJust, первое поле является целым числом, а второе может быть любого типа. Давайте сделаем этот тип экземпляром класса >Functor, так чтобы каждый раз, когда мы используем функцию >fmap, функция применялась ко второму полю, а первое поле увеличивалось на >1:

>instance Functor CMaybe where

>   fmap f CNothing= CNothing

>   fmap f (CJust counter x) = CJust (counter+1) (f x)

Это отчасти похоже на реализацию экземпляра для типа >Maybe, только когда функция >fmap применяется к значению, которое не представляет пустую коробку (значение >CJust), мы не просто применяем функцию к содержимому, но и увеличиваем счётчик на 1. Пока вроде бы всё круто! Мы даже можем немного поиграть с этим:

>ghci> fmap (++"-ха") (CJust 0 "хо")

>CJust 1 "хо-ха"

>ghci> fmap (++"-хе") (fmap (++"-ха") (CJust 0 "хо"))

>CJust 2 "хо-ха-хе"

>ghci> fmap (++"ля") CNothing

>CNothing

Подчиняется ли этот тип законам функторов? Для того чтобы увидеть, что что-то не подчиняется закону, достаточно найти всего одно исключение.

>ghci> fmap id (CJust 0 "ха-ха")

>CJust 1 "ха-ха"

>ghci> id (CJust 0 "ха-ха")

>CJust 0 "ха-ха"

Как гласит первый закон функторов, если мы отобразим значение функтора с помощью функции