Ну а что случится, если мы получим на вход пустую строку? В этом случае выполнится часть после ключевого слова then
. То есть выполнится выражение return ()
. Если вам приходилось писать на императивных языках вроде C, Java или на Python, вы наверняка уверены, что знаете, как работает функция return
– и, возможно, у вас возникнет искушение пропустить эту часть текста. Но не стоит спешить: функция return
в языке Haskell работает совершенно не так, как в большинстве других языков! Её название сбивает с толку, но на самом деле она довольно сильно отличается от своих «тёзок». В императивных языках ключевое слово return
обычно прекращает выполнение метода или процедуры и возвращает некоторое значение вызывающему коду. В языке Haskell (и особенно в действиях ввода-вывода) одноимённая функция создаёт действие ввода-вывода из чистого значения. Если продолжать аналогию с коробками, она берёт значение и помещает его в «коробочку». Получившееся в результате действие ввода-вывода на самом деле не выполняет никаких действий – оно просто инкапсулирует некоторое значение. Таким образом, в контексте системы ввода-вывода return "ха-ха"
будет иметь тип IO String
. Какой смысл преобразовывать чистое значение в действие ввода-вывода, которое ничего не делает? Зачем «пачкать» нашу программу больше необходимого? Нам нужно некоторое действие ввода-вывода для второй части условного оператора, чтобы обработать случай пустой строки. Вот для чего мы создали фиктивное действие ввода-вывода, которое ничего не делает, записав return ()
.
Вызов функции return
не прекращает выполнение блока do
– ничего подобного! Например, следующая программа успешно выполнится вся до последней строчки:
main = do
return ()
return "ХА-ХА-ХА"
line <– getLine
return "ЛЯ-ЛЯ-ЛЯ"
return 4
putStrLn line
Всё, что делает функция return
, – создаёт действия ввода-вывода, которые не делают ничего, кроме как содержат значения, и все они отбрасываются, поскольку не привязаны к образцам. Мы можем использовать функцию return
вместе с символом <–
для того, чтобы связывать значения с образцами.
main = do
let a = "ад"
b = "да!"
putStrLn $ a ++ " " ++ b
Как вы можете видеть, функция return
выполняет обратную операцию по отношению к операции <–
. В то время как функция return
принимает значение и помещает его в «коробку», операция <–
принимает (и исполняет) «коробку», а затем привязывает полученное из неё значение к имени. Но всё это выглядит лишним, так как в блоках do
можно использовать выражение let
для привязки к именам, например так:
main = do
let a = "hell"
b = "yeah"
putStrLn $ a ++ " " ++ b
При работе с блоками do
мы чаще всего используем функцию return
либо для создания действия ввода-вывода, которое ничего не делает, либо для того, чтобы блок do
возвращал нужное нам значение, а не результат последнего действия ввода-вывода. Во втором случае мы используем функцию return
, чтобы создать действие ввода-вывода, которое будет всегда возвращать нужное нам значение, и эта функция return
должна находиться в самом конце блока do
.
Некоторые полезные функции для ввода-вывода
В стандартной библиотеке языка Haskell имеется масса полезных функций и действий ввода-вывода. Давайте рассмотрим некоторые из них и увидим, как ими пользоваться.
Функция putStr
Функция putStr
похожа на функцию putStrLn
– она принимает строку как параметр и возвращает действие ввода-вывода, которое печатает строку на терминале. Единственное отличие: функция putStr
не выполняет перевод на новую строку после печати, как это делает putStrLn
.
main = do
putStr "Привет, "
putStr "я "
putStrLn "Энди!"
Если мы скомпилируем эту программу, то при запуске получим:
Привет, я Энди!
Функция putChar
Функция putChar
принимает символ и возвращает действие ввода-вывода, которое напечатает его на терминале.
main = do
putChar 'A'
putChar 'Б'
putChar 'В'
Функция putStr
определена рекурсивно с помощью функции putChar
. Базовый случай для функции putStr
– это пустая строка. Если печатаемая строка пуста, функция возвращает пустое действие ввода-вывода, то есть return ()
. Если строка не пуста, функция выводит на терминал первый символ этой строки, вызывая функцию putChar
, а затем выводит остальные символы, снова рекурсивно вызывая саму себя.
putStr :: String –> IO ()
putStr [] = return ()
putStr (x:xs) = do
putChar x
putStr xs