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

. Функция >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 мы говорили о том, что действия ввода-вывода похожи на ящики с маленькими ножками, которые выходят наружу и приносят нам какое-то значение из внешнего мира. Мы можем посмотреть, что они принесли, но после просмотра нам необходимо снова обернуть значение в тип