Возможность поместить в программу всего один оператор ввода-вывода не очень-то вдохновляет. Но мы можем использовать ключевое слово >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 чётко разделяет чистую и «грязную» части кода. Функция