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
не нужна. Так вот, в блоках 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