Каким же образом функции выступают в качестве функторов? Давайте взглянем на реализацию, которая находится в модуле Control.Monad.Instances.
instance Functor ((–>) r) where
fmap f g = (\x –> f (g x))
Сначала подумаем над типом метода fmap:
fmap :: (a –> b) –> f a –> f b
Далее мысленно заменим каждое вхождение идентификатора f, являющегося ролью, которую играет наш экземпляр функтора, выражением (–>) r. Это позволит нам понять, как функция fmap должна вести себя в отношении данного конкретного экземпляра. Вот результат:
fmap :: (a –> b) –> ((–>) r a) –> ((–>) r b)
Теперь можно записать типы (–>) r a и (–>) r b в инфиксном виде, то есть r –> a и r –> b, как мы обычно поступаем с функциями:
fmap :: (a –> b) –> (r –> a) –> (r –> b)
Хорошо. Отображение одной функции с помощью другой должно произвести функцию, так же как отображение типа Maybe с помощью функции должно произвести тип Maybe, а отображение списка с помощью функции – список. О чём говорит нам предыдущий тип? Мы видим, что он берёт функцию из a в b и функцию из r в a и возвращает функцию из r в b. Напоминает ли это вам что-нибудь? Да, композицию функций!.. Мы присоединяем выход r –> a ко входу a –> b, чтобы получить функцию r –> b, чем в точности и является композиция функций. Вот ещё один способ записи этого экземпляра:
instance Functor ((–>) r) where
fmap = (.)
Код наглядно показывает, что применение функции fmap к функциям – это просто композиция функций.
В исходном коде импортируйте модуль Control.Monad.Instances, поскольку это модуль, где определён данный экземпляр, а затем загрузите исходный код и попробуйте поиграть с отображением функций:
ghci> :t fmap (*3) (+100)
fmap (*3) (+100) :: (Num a) => a –> a
ghci> fmap (*3) (+100) 1
303
ghci> (*3) `fmap` (+100) $ 1
303
ghci> (*3) . (+100) $ 1
303
ghci> fmap (show . (*3)) (*100) 1
"300"
Мы можем вызывать fmap как инфиксную функцию, чтобы сходство с оператором . было явным. Во второй строке ввода мы отображаем (+100) с помощью (*3), что даёт функцию, которая примет ввод, применит к нему (+100), а затем применит к этому результату (*3). Затем мы применяем эту функцию к значению 1.
Как и все функторы, функции могут восприниматься как значения с контекстами. Когда у нас есть функция вроде (+3), мы можем рассматривать значение как окончательный результат функции, а контекстом является то, что мы должны применить эту функцию к чему-либо, чтобы получить результат. Применение fmap (*3) к (+100) создаст ещё одну функцию, которая действует так же, как (+100), но перед возвратом результата к этому результату будет применена функция (*3).
Тот факт, что функция fmap является композицией функций при применении к функциям, на данный момент не слишком нам полезен, но, по крайней мере, он вызывает интерес. Это несколько меняет наше сознание и позволяет нам увидеть, как сущности, которые действуют скорее как вычисления, чем как коробки (IO и (–>) r), могут быть функторами. Отображение вычисления с помощью функции возвращает тот же самый тип вычисления, но результат этого вычисления изменён функцией.
Перед тем как перейти к законам, которым должна следовать fmap, давайте ещё раз задумаемся о типе fmap:
fmap :: (a –> b) –> f a –> f b
Если помните, введение в каррированные функции в главе 5 началось с утверждения, что все функции в языке Haskell на самом деле принимают один параметр. Функция a –> b –> c в действительности берёт только один параметр типа a, после чего возвращает функцию b –> c, которая принимает один параметр типа b и возвращает значение типа c. Вот почему вызов функции с недостаточным количеством параметров (её частичное применение) возвращает нам обратно функцию, принимающую несколько параметров, которые мы пропустили (если мы опять воспринимаем функции так, как если бы они принимали несколько параметров). Поэтому a –> b –> c можно записать в виде a –> (b –> c), чтобы сделать каррирование более очевидным.