Преобразование входного потока

Подобная последовательность действий – считывание строки из потока ввода, преобразование её функцией и вывод результата – настолько часто встречается, что существует функция, которая делает эту задачу ещё легче; она называется interact. Функция interact принимает функцию типа String –> String как параметр и возвращает действие ввода-вывода, которое примет некоторый вход, запустит заданную функцию и распечатает результат. Давайте изменим нашу программу так, чтобы воспользоваться этой функцией:

main = interact shortLinesOnly

shortLinesOnly :: String -> String

shortLinesOnly = unlines . filter (line -> length line < 15) . lines

Этой программой можно пользоваться, либо перенаправляя файл в поток ввода, либо вводя данные непосредственно с клавиатуры, строка за строкой. Результат будет одинаковым, однако при вводе с клавиатуры входные данные будут чередоваться с выходными.

Давайте напишем программу, которая постоянно считывает строку и затем говорит нам, является ли введённая строка палиндромом. Можно было бы использовать функцию getLine, чтобы она считывала строку, затем говорить пользователю, является ли она палиндромом, и снова запускать функцию main. Но легче делать это с помощью функции interact. Когда вы её используете, всегда думайте, как преобразовать некий вход в желаемый выход. В нашем случае мы хотим заменить строку на входе на "палиндром" или "не палиндром".

respondPalindromes :: String -> String

respondPalindromes =

   unlines .

   map (xs -> if isPal xs then "палиндром" else "не палиндром") .

   lines

isPal xs = xs == reverse xs

Всё вполне очевидно. Вначале преобразуем строку, например

"слон потоп что-нибудь"

в список строк

["слон", "потоп", "что-нибудь"]

Затем применяем анонимную функцию к элементам списка и получаем:

["не палиндром", "палиндром", "не палиндром"]

Соединяем список обратно в строку функцией unlines. Теперь мы можем определить главное действие ввода-вывода:

main = interact respondPalindromes

Протестируем:

$ ./palindromes

ха-ха

не палиндром

арозаупаланалапуазора

палиндром

печенька

не палиндром

Хоть мы и написали программу, которая преобразует одну большую составную строку в другую составную строку, она работает так, как будто мы обрабатываем строку за строкой. Это потому что язык Haskell ленив – он хочет распечатать первую строку результата, но не может, поскольку пока не имеет первой строки ввода. Как только мы введём первую строку на вход, он напечатает первую строку на выходе. Мы выходим из программы по символу конца файла.

Также можно запустить нашу программу, перенаправив в неё содержимое файла. Например, у нас есть файл words.txt:

кенгуру

радар

ротор

мадам

Вот что мы получим, если перенаправим его на вход нашей программы:

$ ./palindromes < words.txt

не палиндром

палиндром

палиндром

палиндром

Ещё раз: результат аналогичен тому, как если бы мы запускали программу и вводили слова вручную. Здесь мы не видим входных строк, потому что вход берётся из файла, а не со стандартного ввода.

К этому моменту, вероятно, вы уже усвоили, как работает ленивый ввод-вывод и как его можно использовать с пользой для себя. Вы можете рассуждать о том, каким должен быть выход для данного входа, и писать функцию для преобразования входа в выход. В ленивом вводе-выводе ничего не считывается со входа до тех пор, пока это не станет абсолютно необходимым для того, что мы собираемся напечатать.