Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программирование » Изучай Haskell во имя добра! - Миран Липовача

Изучай Haskell во имя добра! - Миран Липовача

Читать онлайн Изучай Haskell во имя добра! - Миран Липовача

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 35 36 37 38 39 40 41 42 43 ... 96
Перейти на страницу:

В третьей строке можно видеть ещё один не встречавшийся нам ранее элемент синтаксиса, 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.

Использование ключевого слова let внутри блока do

Помните связывания при помощи ключевого слова let? Если уже подзабыли, освежите свои знания. Связывания должны быть такого вида: let <определения> in <выражение>, где <определения> – это имена, даваемые выражениям, а <выражение> использует имена из <определений>. Также мы говорили, что в списковых выражениях часть in не нужна. Так вот, в блоках do можно использовать выражение let таким же образом, как и в списковых выражениях. Смотрите:

import Data.Char

main = do

   putStrLn "Ваше имя?"

   firstName <– getLine

   putStrLn "Ваша фамилия?"

   lastName <– getLine

   let bigFirstName = map toUpper firstName

       bigLastName = map toUpper lastName

   putStrLn $ "Привет, " ++ bigFirstName ++ " "

                         ++ bigLastName

                         ++ ", как дела?"

Видите, как выровнены операторы действий ввода-вывода в блоке do? Обратите внимание и на то, как выровнено выражение let по отношению к действиям ввода-вывода и как выровнены образцы внутри выражения let. Это хороший пример, потому что выравнивание текста очень важно в языке Haskell. Далее мы записали вызов map toUpper firstName, что превратит, например, "Иван" в намного более солидное "ИВАН". Мы связали эту строку в верхнем регистре с именем, которое использовали в дальнейшем при выводе на терминал.

Вам может быть непонятно, когда использовать символ <–, а когда выражение let. Запомните: символ <– (в случае действий ввода-вывода) используется для выполнения действий ввода-вывода и связывания результатов с именами. Выражение map toUpper firstName не является действием ввода-вывода – это чистое выражение. Соответственно, используйте символ <– для связывания результатов действий ввода-вывода с именами, а выражение let – для связывания имён с чистыми значениями. Если бы мы выполнили что-то вроде let firstName = getLine, то просто создали бы синоним функции getLine, для которого значение всё равно должно получаться с помощью символа <–.

Обращение строк

Теперь напишем программу, которая будет считывать строки, переставлять в обратном порядке буквы в словах и распечатывать их. Выполнение программы прекращается при вводе пустой строки. Итак:

main = do

   line <– getLine

1 ... 35 36 37 38 39 40 41 42 43 ... 96
Перейти на страницу:
Тут вы можете бесплатно читать книгу Изучай Haskell во имя добра! - Миран Липовача.
Комментарии