В примере forever
. Если мы перейдём на getContents
, то она возьмёт на себя все заботы о деталях ввода-вывода – о том, когда и какую часть входных данных нужно прочитать. Поскольку наша программа просто берёт входные данные, преобразует их и выводит результат, пользуясь getContents
, её можно написать короче:
import Data.Char
main = do
contents <- getContents
putStr $ map toUpper contents
Мы выполняем действие getContents
и даём имя contents
строке, которую она прочтёт. Затем проходим функцией toUpper
по всем символам этой строки и выводим результат на терминал. Имейте в виду: поскольку строки являются списками, а списки ленивы, как и действие getContents
, программа не будет пытаться прочесть и сохранить в памяти всё содержимое входного потока. Вместо этого она будет читать данные порциями, переводить каждую порцию в верхний регистр и печатать результат.
Давайте проверим:
$ ./capslocker < haiku.txt
Я МАЛЕНЬКИЙ ЧАЙНИК
ОХ УЖ ЭТОТ ОБЕД В САМОЛЁТЕ
ОН СТОЛЬ МАЛ И НЕВКУСЕН
Работает. А что если мы просто запустим
$ ./capslocker
хей хо
ХЕЙ ХО
идём
ИДЁМ
Чудесно! Как видите, программа печатает строки в верхнем регистре по мере ввода строк. Когда результат действия getContents
связывается с идентификатором сontents
, он представляется в памяти не в виде настоящей строки, но в виде обещания, что рано или поздно он вернёт строку. Также есть обещание применить функцию toUpper
ко всем символам строки сontents
. Когда выполняется функция putStr
, она говорит предыдущему обещанию: «Эй, мне нужна строка в верхнем регистре!». Поскольку никакой строки ещё нет, она говорит идентификатору сontents
: «Аллё, а не считать ли строку с терминала?». Вот тогда функция getContents
в самом деле считывает с терминала и передаёт строку коду, который её запрашивал, чтобы сделать что-нибудь осязаемое. Затем этот код применяет функцию toUpper
к символам строки и отдаёт результат в функцию putStr
, которая его печатает. После чего функция putStr
говорит, «Ау, мне нужна следующая строка, шевелись!» – и так продолжается до тех пор, пока не закончатся строки на входе, что мы обозначаем символом конца файла.
Теперь давайте напишем программу, которая будет принимать некоторый вход и печатать только те строки, длина которых меньше 15 символов. Смотрим:
main = do
contents <- getContents
putStr $ shortLinesOnly contents
shortLinesOnly :: String -> String
shortLinesOnly = unlines . filter (\line -> length line < 15) . lines
Фрагмент программы, ответственный за ввод-вывод, сделан настолько малым, насколько это вообще возможно. Так как предполагается, что наша программа печатает результат, основываясь на входных данных, её можно реализовать согласно следующей логике: читаем содержимое входного потока, запускаем на этом содержимом некоторую функцию, печатаем результат работы этой функции.
Функция shortLinesOnly
принимает строку – например, такую: "коротко\nдлииииииииииинно\nкоротко"
. В этом примере в строке на самом деле три строки входных данных: две короткие и одна (посередине) длинная. В результате применения функции lines
получаем список ["коротко", "длииииииииииинно", "коротко"]
. Затем список строк фильтруется, и остаются только строки, длина которых меньше 15 символов: ["коротко", "коротко"]
. Наконец, функция unlines
соединяет элементы списка в одну строку, разделяя их символом перевода строки: "коротко\nкоротко"
.
Попробуем проверить, что получилось. Сохраните этот текст в файле
Я короткая
И я
А я длиииииииинная!!!
А уж я-то какая длиннющая!!!!!!!
Коротенькая
Длиииииииииииииииииииииинная
Короткая
Сохраните программу в файле
$ ghc shortlinesonly.hs
[1 of 1] Compiling Main ( shortlinesonly.hs, shortlinesonly.o )
Linking shortlinesonly ...
Чтобы её протестировать, перенаправим содержимое файла
$ ./shortlinesonly < shortlines.txt
Я короткая
И я
Коротенькая
Короткая