. Функция
>fmap
имеет тип
>fmap :: (a –> b) –> f a –> f b
, который говорит: «Дайте мне функцию, которая принимает
>a
и возвращает
>b
и коробку, где содержится
>a
(или несколько a), и я верну коробку с
>b
(или несколькими
>b
) внутри». Она применяет функцию к элементу внутри коробки.
Мы также можем воспринимать значения функторов как значения с добавочным контекстом. Например, значения типа >Maybe
обладают дополнительным контекстом того, что вычисления могли окончиться неуспешно. По отношению к спискам контекстом является то, что значение может быть множественным либо отсутствовать. Функция >fmap
применяет функцию к значению, сохраняя его контекст.
Если мы хотим сделать конструктор типа экземпляром класса >Functor
, он должен иметь сорт >*
>–>
>*
; это значит, что он принимает ровно один конкретный тип в качестве параметра типа. Например, конструктор >Maybe
может быть сделан экземпляром, так как он получает один параметр типа для произведения конкретного типа, как, например, >Maybe Int
или >Maybe String
. Если конструктор типа принимает два параметра, как, например, конструктор >Either
, мы должны частично применять конструктор типа до тех пор, пока он не будет принимать только один параметр. Поэтому мы не можем написать определение >Functor Either where
, зато можем написать определение >Functor (Either a) where
. Затем, если бы мы вообразили, что функция >fmap
предназначена только для работы со значениями типа >Either a
, она имела бы следующее описание типа:
>fmap :: (b –> c) –> Either a b –> Either a c
Как видите, часть >Either
>a
– фиксированная, потому что частично применённый конструктор типа >Either a
принимает только один параметр типа.
Действия ввода-вывода в качестве функторов
К настоящему моменту вы изучили, каким образом многие типы (если быть точным, конструкторы типов) являются экземплярами класса >Functor: []
и >Maybe
, >Either a
, равно как и тип >Tree
, который мы создали в главе 7. Вы видели, как можно отображать их с помощью функций на всеобщее благо. Теперь давайте взглянем на экземпляр типа >IO
.
Если какое-то значение обладает, скажем, типом >IO String
, это означает, что перед нами действие ввода-вывода, которое выйдет в реальный мир и получит для нас некую строку, которую затем вернёт в качестве результата. Мы можем использовать запись ><–
в синтаксисе >do
для привязывания этого результата к имени. В главе 8 мы говорили о том, что действия ввода-вывода похожи на ящики с маленькими ножками, которые выходят наружу и приносят нам какое-то значение из внешнего мира. Мы можем посмотреть, что они принесли, но после просмотра нам необходимо снова обернуть значение в тип