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

>ghci> fmap (++ " ПРИВЕТ, Я ВНУТРИ JUST") Nothing

>Nothing

>ghci> fmap (*2) (Just 200)

>Just 400

>ghci> fmap (*2) Nothing

>Nothing

Деревья тоже являются функторами

Ещё один тип, который можно отображать и сделать для него экземпляр класса >Functor, – это наш тип >Tree. Дерево может хранить ноль или более других элементов, и конструктор типа >Tree принимает один тип-параметр. Если бы мы хотели записать функцию >fmap только для типа >Tree, её сигнатура выглядела бы так: >(a>–>>b)>–>>Tree>a>–>>Tree>b.

Для этой функции нам потребуется рекурсия. Отображение пустого дерева возвращает пустое дерево. Отображение непустого дерева – это дерево, состоящее из результата применения функции к корневому элементу и из правого и левого поддеревьев, к которым также было применено отображение.

>instance Functor Tree where

>   fmap f EmptyTree = EmptyTree

>   fmap f (Node x left right) = Node (f x) (fmap f left) (fmap f right)

Проверим:

>ghci> fmap (*2) EmptyTree

>EmptyTree

>ghci> fmap (*4) (foldr treeInsert EmptyTree [5,7,3])

>Node 20 (Node 12 EmptyTree EmptyTree) (Node 28 EmptyTree EmptyTree)

Впрочем, тут следует быть внимательным! Если тип >Tree используется для представления бинарного дерева поиска, то нет никакой гарантии, что дерево останется таковым после применения к каждому его узлу некоторой функции. Проход по дереву функцией, скажем, >negate превратит дерево поиска в обычное дерево.

И тип Either является функтором

Отлично! Ну а теперь как насчёт >Either a b? Можно ли сделать его функтором? Класс типов >Functor требует конструктор типов с одним параметром, а у типа >Either их два. Гм-м… Придумал – мы частично применим конструктор >Either, «скормив» ему один параметр, и таким образом он получит один свободный параметр. Вот как для типа >Either определён экземпляр класса >Functor в стандартных библиотеках:

>instance Functor (Either a) where

>   fmap f (Right x) = Right (f x)

>   fmap f (Left x) = Left x

Что же здесь происходит? Как видно из записи, мы сделали экземпляр класса не для типа >Either, а для >Either a. Это потому, что >Either – конструктор типа, который принимает два параметра, а >Either a – только один. Если бы функция >fmap была только для >Either>a, сигнатура типа выглядела бы следующим образом:

>(b –> c) –> Either a b –> Either a c

поскольку это то же самое, что

>(b –> c) –> (Either a) b –> (Either a) c

В реализации мы выполняем отображение в конструкторе данных >Right, но не делаем этого в >Left. Почему? Вспомним, как определён тип >Either>a>b:

>data Either a b = Left a | Right b

Если мы хотим применять некую функцию к обеим альтернативам, параметры