Действие, которое производит блок do, будет всегда возвращать результирующее значение своего последнего действия. Вот почему мы используем функцию return, чтобы создать действие ввода-вывода, которое в действительности ничего не делает, а просто возвращает применение f result в качестве результата нового действия ввода-вывода. Взгляните на этот кусок кода:

main = do

   line <– getLine

   let line' = reverse line

   putStrLn $ "Вы сказали " ++ line' ++ " наоборот!"

   putStrLn $ "Да, вы точно сказали " ++ line' ++ " наоборот!"

У пользователя запрашивается строка, и мы отдаём её обратно пользователю, но в перевёрнутом виде. А вот как можно переписать это с использованием функции fmap:

main = do

   line <– fmap reverse getLine

   putStrLn $ "Вы сказали " ++ line ++ " наоборот!"

   putStrLn $ "Да, вы точно сказали " ++ line ++ " наоборот!"

Так же как можно отобразить Just "уфф" с помощью отображения fmap reverse, получая Just "ффу", мы можем отобразить и функцию getLine с помощью отображения fmap reverse. Функция getLine – это действие ввода-вывода, которое имеет тип IO String, и отображение его с помощью функции reverse даёт нам действие ввода-вывода, которое выйдет в реальный мир и получит строку, а затем применит функцию reverse к своему результату. Таким же образом, как мы можем применить функцию к тому, что находится внутри коробки Maybe, можно применить функцию и к тому, что находится внутри коробки IO, но она должна выйти в реальный мир, чтобы получить что-либо. Затем, когда мы привязываем результат к имени, используя запись <–, имя будет отражать результат, к которому уже применена функция reverse.

Действие ввода-вывода fmap (++"!") getLine ведёт себя в точности как функция getLine, за исключением того, что к её результату всегда добавляется строка "!" в конец!

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

Предположим, вы связываете результат действия ввода-вывода с именем лишь для того, чтобы применить к нему функцию, а затем даёте очередному результату какое-то другое имя, – в таком случае подумайте над использованием функции fmap. Если вы хотите применить несколько функций к некоторым данным внутри функтора, то можете объявить свою функцию на верхнем уровне, создать анонимную функцию или, в идеале, использовать композицию функций:

import Data.Char

import Data.List

main = do

   line <– fmap (intersperse '-' . reverse . map toUpper) getLine

   putStrLn line

Вот что произойдёт, если мы сохраним этот код в файле fmapping_io.hs, скомпилируем, запустим и введём "Эй, привет":

$ ./fmapping_io

Эй, привет

Т-Е-В-И-Р-П- -,-Й-Э

Выражение intersperse '-' . reverse . map toUpper берёт строку, отображает её с помощью функции toUpper, применяет функцию reverse к этому результату, а затем применяет к нему выражение intersperse '-'. Это более красивый способ записи следующего кода:

(\xs –> intersperse '-' (reverse (map toUpper xs)))

<p>Функции в качестве функторов</p>

Другим экземпляром класса Functor, с которым мы всё время имели дело, является (–>) r. Стойте!.. Что, чёрт возьми, означает (–>) r? Тип функции r –> a может быть переписан в виде (–>) r a, так же как мы можем записать 2 + 3 в виде (+) 2 3. Когда мы воспринимаем его как (–>) r a, то (–>) представляется немного в другом свете. Это просто конструктор типа, который принимает два параметра типа, как это делает конструктор Either.

Но вспомните, что конструктор типа должен принимать в точности один параметр типа, чтобы его можно было сделать экземпляром класса Functor. Вот почему нельзя сделать конструктор (–>) экземпляром класса Functor; однако, если частично применить его до (–>) r, это не составит никаких проблем. Если бы синтаксис позволял частично применять конструкторы типов с помощью сечений – подобно тому как можно частично применить оператор +, выполнив (2+), что равнозначно (+) 2, – вы могли бы записать (–>) r как (r –>).

Перейти на страницу:

Похожие книги