++ ", как дела?"
Видите, как выровнены операторы действий ввода-вывода в блоке do
? Обратите внимание и на то, как выровнено выражение let
по отношению к действиям ввода-вывода и как выровнены образцы внутри выражения let
. Это хороший пример, потому что выравнивание текста очень важно в языке Haskell. Далее мы записали вызов map
toUpper
firstName
, что превратит, например, "Иван"
в намного более солидное "ИВАН"
. Мы связали эту строку в верхнем регистре с именем, которое использовали в дальнейшем при выводе на терминал.
Вам может быть непонятно, когда использовать символ <–
, а когда выражение let
. Запомните: символ <–
(в случае действий ввода-вывода) используется для выполнения действий ввода-вывода и связывания результатов с именами. Выражение map toUpper firstName
не является действием ввода-вывода – это чистое выражение. Соответственно, используйте символ <–
для связывания результатов действий ввода-вывода с именами, а выражение let
– для связывания имён с чистыми значениями. Если бы мы выполнили что-то вроде let firstName = getLine
, то просто создали бы синоним функции getLine
, для которого значение всё равно должно получаться с помощью символа <–
.
Обращение строк
Теперь напишем программу, которая будет считывать строки, переставлять в обратном порядке буквы в словах и распечатывать их. Выполнение программы прекращается при вводе пустой строки. Итак:
main = do
line <– getLine
if null line
then return ()
else do
putStrLn $ reverseWords line
main
reverseWords :: String –> String
reverseWords = unwords . map reverse . words
Чтобы лучше понять, как работает программа, сохраните её в файле
$ ghc reverse.hs
[1 of 1] Compiling Main ( reverse.hs, reverse.o )
Linking reverse ...
$ ./reverse
уберитесь в проходе номер 9
ьсетиребу в едохорп ремон 9
козёл ошибки осветит твою жизнь
лёзок икбишо титевсо юовт ьнзиж
но это всё мечты
он отэ ёсв ытчем
Для начала посмотрим на функцию reverseWords
. Это обычная функция, которая принимает строку, например "эй ты мужик"
, и вызывает функцию words
, чтобы получить список слов ["эй", "ты","мужик"]
. Затем мы применяем функцию reverse
к каждому элементу списка, получаем ["йэ","ыт","кижум"]
и помещаем результат обратно в строку, используя функцию unwords
. Конечным результатом будет "йэ ыт кижум"
.
Теперь посмотрим на функцию main
. Сначала мы получаем строку с терминала с помощью функции getLine
. Далее у нас имеется условное выражение. Запомните, что в языке Haskell каждое ключевое слово if
должно сопровождаться секцией else
, так как каждое выражение должно иметь некоторое значение. Наш оператор записан так, что если условие истинно (в нашем случае – когда введут пустую строку), мы выполним одно действие ввода-вывода; если оно ложно – выполним действие ввода-вывода из секции else
. По той же причине в блоке do
условные операторы if
должны иметь вид if <
.
Вначале посмотрим, что делается в секции else
. Поскольку можно поместить только одно действие ввода-вывода после ключевого слова else
, мы используем блок do
для того, чтобы «склеить» несколько операторов в один. Эту часть можно было бы написать так:
else (do
putStrLn $ reverseWords line
main)
Подобная запись явно показывает, что блок do
может рассматриваться как одно действие ввода-вывода, но и выглядит она не очень красиво. В любом случае внутри блока do
мы можем вызвать функцию reverseWords
со строкой – результатом действия getLine
и распечатать результат. После этого мы выполняем функцию main
. Получается, что функция main
вызывается рекурсивно, и в этом нет ничего необычного, так как сама по себе функция main
– тоже действие ввода-вывода. Таким образом, мы возвращаемся к началу программы в следующей рекурсивной итерации.