ТОП 10:

Объединение действий ввода-вывода



 
 

Возможность поместить в программу всего один оператор ввода- вывода не очень-то вдохновляет. Но мы можем использовать клю- чевое слово do для того, чтобы «склеить» несколько операторов ввода-вывода в один. Рассмотрим пример:

main = do

putStrLn "Привет, как тебя зовут?" name <– getLine

 
 

putStrLn ("Привет, " ++ name ++ ", ну ты и хипстота!")

О, новый синтаксис!.. И он похож на синтаксис императивных языков. Если откомпилировать и запустить эту программу, она бу- дет работать так, как вы и предполагаете. Обратите внимание: мы записали ключевое слово do и затем последовательность шагов, как сделали бы в императивном языке. Каждый из этих шагов – дейст- вие ввода-вывода. Расположив их рядом с помощью ключевого сло- ва do, мы свели их в одно действие ввода-вывода. Получившееся действие имеет тип IO(); это тип последнего оператора в цепочке.


 

По этой причине функция main всегда имеет тип main :: IO

<нечто>, где <нечто> – некоторый конкретный тип. По общеприня- тому соглашению обычно не пишут декларацию типа для функции main.

 
 

В третьей строке можно видеть ещё один не встречавшийся нам ранее элемент синтаксиса, name <– getLine. Создаётся впечатле- ние, будто считанная со стандартного входа строка сохраняется в переменной с именем name. Так ли это на самом деле? Давайте пос- мотрим на тип getLine.

ghci> :t getLine getLine :: IO String

Ага!.. Функция getLine – действие ввода-вывода, которое содер- жит результирующий тип – строку. Это понятно: действие ждёт, пока пользователь не введёт что-нибудь с терминала, и затем это нечто будет представлено как строка. Что тогда делает выражение

name <– getLine? Можно прочи- тать его так: «выполнить дейс- твие getLine и затем связать ре- зультат выполнения с именем name». Функция getLine имеет тип IO String, поэтому образец name будет иметь тип String. Можно представить действие ввода-вы- вода в виде ящика с ножками, который ходит в реальный мир, что-то в нём делает (рисует граф- фити на стене, например) и иног- да приносит обратно какие-либо данные. Если ящик что-либо при-

нёс, единственный способ открыть его и извлечь данные – исполь- зовать конструкцию с символом <–. Получить данные из действия ввода-вывода можно только внутри другого действия ввода-вывода. Таким образом, язык Haskell чётко разделяет чистую и «грязную» части кода. Функция getLine – не чистая функция, потому что её результат может быть неодинаковым при последовательных вызо- вах. Вот почему она как бы «запачкана» конструктором типов IO, и мы можем получить данные только внутри действий ввода-вывода,


 

имеющих в сигнатуре типа маркер IO. Так как код для ввода-выво- да также «испачкан», любое вычисление, зависящее от «испачкан- ных» IO-данных, также будет давать «грязный»результат.

Если я говорю «испачканы», это не значит, что мы не сможем использовать результат, содержащийся в типе IO в чистом коде. Мы временно «очищаем» данные внутри действия, когда связываем их с именем. В выражении name <– getLine образец name содержит обыч- ную строку, представляющую содержимое ящика.

 
 

Мы можем написать сложную функцию, которая, скажем, при- нимает ваше имя как параметр (обычная строка) и предсказывает вашу удачливость или будущее всей вашей жизни, основываясь на имени:

main = do

putStrLn "Привет, как тебя зовут?" name <– getLine

 
 

putStrLn $ "Вот твоё будущее: " ++ tellFortune name

Функция tellFortune (или любая другая, которой мы передаём значение name) не должна знать ничего про IO – это обычная функ- ция String –> String.

 
 

Посмотрите на этот образец кода. Корректен ли он?

nameTag = "Привет, меня зовут " ++ getLine

Если вы ответили «нет», возьмите с полки пирожок. Если отве- тили «да», убейте себя об стену... Шучу, не надо! Это выражение не сработает, потому что оператор ++ требует, чтобы оба параметра были списками одинакового типа. Левый параметр имеет тип String (или [Char], если вам угодно), в то время как функция getLine воз- вращает значение типа IO String. Вы не сможете конкатенировать строку и результат действия ввода-вывода. Для начала нам нужно извлечь результат из действия ввода-вывода, чтобы получить зна- чение типа String, и единственный способ сделать это – выполнить что-то вроде name <– getLine внутри другого действия ввода-вывода. Если мы хотим работать с «нечистыми» данными, то должны де- лать это в «нечистом» окружении!… Итак, грязь от нечистоты рас- пространяется как моровое поветрие, и в наших интересах делать часть для осуществления ввода-вывода настолько малой, насколько это возможно.


 

 
 

Каждое выполненное действие ввода-вывода заключает в себе ре- зультат. Вот почему наш предыдущий пример можно переписать так:

main = do

foo <- putStrLn "Привет, как тебя зовут?" name <– getLine

 
 

putStrLn ("Привет, " ++ name ++ ", ну ты и хипстота!")

Тем не менее образец foo всегда будет получать значение (), так что большого смысла в этом нет. Заметьте: мы не связываем послед- ний вызов функции putStrLn с именем, потому что в блоке do пос- ледний оператор, в отличие от предыдущих, не может быть связан с именем. Мы узнаем причины такого поведения немного позднее, когда познакомимся с миром монад. До тех пор можно считать, что блок do автоматически получает результат последнего оператора и возвращает его в качестве собственного результата.

За исключением последней строчки, каждая строка в блоке do может быть использована для связывания. Например, putStrLn "ЛЯ" может быть записана как _ <– putStrLn "ЛЯ". Но в этом нет никакого смысла, так что мы опускаем <– для действий ввода-вывода, не воз- вращающих значимого результата.

 
 

Иногда начинающие думают, что вызов

myLine = getLine

считает значение со стандартного входа и затем свяжет это значе- ние с именем myLine. На самом деле это не так. Такая запись даст фун- кции getLine другое синонимичное имя, в данном случае – myLine. Запомните: чтобы получить значение из действия ввода-вывода, вы должны выполнять его внутри другого действия ввода-вывода и свя- зывать его с именем при помощи символа <–.

Действие ввода-вывода будет выполнено, только если его имя main или если оно помещено в составное действие с помощью блока do. Также мы можем использовать блок do для того, чтобы «скле- ить» несколько действий ввода-вывода в одно. Затем можно будет использовать его в другом блоке do и т. д. В любом случае действие будет выполнено, только если оно каким-либо образом вызывается из функции main.

Ах, да, есть ещё один способ выполнить действие ввода-выво- да! Если напечатать его в интерпретаторе GHCi и нажать клавишу Enter, действие выполнится.


 

 
 

gchi> putStrLn "При-и-и-вет" При-и-и-вет

Даже если мы просто наберём некоторое число или вызовем некоторую функцию в GHCi и нажмём Enter, интерпретатор GHCi вычислит значение, затем вызовет для него функцию show, чтобы получить строку, и напечатает строку на терминале, используя функцию putStrLn.

 







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

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