ТОП 10:

Действия ввода-вывода в качестве функторов



К настоящему моменту вы изучили, каким образом многие типы (если быть точным, конструкторы типов) являются экземплярами класса Functor: [] и Maybe, Either a, равно как и тип Tree, который мы создали в главе 7. Вы видели, как можно отображать их с помо- щью функций на всеобщее благо. Теперь давайте взглянем на экзем- пляр типа IO.

Если какое-то значение обладает, скажем, типом IO String, это означает, что перед нами действие ввода-вывода, которое выйдет в реальный мир и получит для нас некую строку, которую затем вер- нёт в качестве результата. Мы можем использовать запись <– в син- таксисе do для привязывания этого результата к имени. В главе 8 мы говорили о том, что действия ввода-вывода похожи на ящики с маленькими ножками, которые выходят наружу и приносят нам ка- кое-то значение из внешнего мира. Мы можем посмотреть, что они принесли, но после просмотра нам необходимо снова обернуть зна- чение в тип IO. Рассматривая эту аналогию с ящиками на ножках, вы можете понять, каким образом тип IO действует как функтор.

 
 

Давайте посмотрим, как же это тип IO является экземпляром класса Functor... Когда мы используем функцию fmap для отображе- ния действия ввода-вывода с помощью функции, мы хотим получить обратно действие ввода-вывода, которое делает то же самое, но к его результирующему значению применяется наша функция. Вот код:

instance Functor IO where fmap f action = do

 
 

result <– action return (f result)

Результатом отображения действия ввода-вывода с помощью чего-либо будет действие ввода-вывода, так что мы сразу же исполь- зуем синтаксис do для склеивания двух действий и создания одного нового. В реализации для метода fmap мы создаём новое действие ввода-вывода, которое сначала выполняет первоначальное дейст- вие ввода-вывода, давая результату имя result. Затем мы выполняем


 

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

 
 

Действие, которое производит блок 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)))


 







Последнее изменение этой страницы: 2017-02-17; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 35.175.121.230 (0.004 с.)