Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MakeevGA-Haskell-a4-shortcode_2014_05_31.doc
Скачиваний:
29
Добавлен:
19.01.2023
Размер:
1.79 Mб
Скачать

Пример программы, производящей нетривиальное преобразование текстового файла

Давайте напоследок посмотрим, как можно легко писать простенькие утилиты, анализирующие и преобразующие текстовые файлы.

Для этого нам понадобится несколько вспомогательных функций:

lines :: String -> [String]

words :: String -> [String]

unlines :: [String] -> String

unwords :: [String] -> String

Функция lines разбивает строку на список строк в тех местах, где встречается символ перевода строки '\n', – очень полезная функция, если все содержимое файла читается разом одной функцией readFile. Функция words разбивает строку на список строк в тех местах где встречается пробел, табуляция или что-то подобное. Функции unlines и unwords производят обратные преобразования.

Итак, напишем, например, функцию, которая читает текстовый файл, и выводит на экран список строк, причем в каждой строке находится число слов в этой строке:

main :: IO ()

main = do

contents <- readFile "input.txt"

putStr (process contents)

process :: String -> String

process =

unlines .

map show .

map length .

map words .

lines

Пусть у нас есть файл input.txt с таким содержанием:

It was the lark, the herald of the morn,

No nightingale. Look, love, what envious streaks

Do lace the severing clouds in yonder east.

Night's candles are burnt out, and jocund day

Stands tiptoe on the misty mountain tops.

I must be gone and live, or stay and die.

Запустим функцию main:

SomeModule> main

9

7

8

8

7

10

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

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

Сначала строка со всем содержимым передается функции lines, и из нее получается список строк [String]:

["It was the lark, the herald of the morn,",

...

"I must be gone and live, or stay and die."]

Потом к этому списку строк применяется функция map words, – то есть, по сути, к каждой строке этого списка применяется функция words, и получается список списков строк [[String]]:

[["It","was","the","lark,","the","herald","of","the","morn,"],

...

["I","must","be","gone","and","live,","or","stay","and","die."]]

Дальше к этому списку применяется функция map length, то есть к каждому подсписку применяется функция length, и получается список целых чисел [Int]:

[9,

...

10]

Далее к этому списку применяется функция map show, а она применяет show к каждому элементу списка, и получается опять список строк [String]:

["9",

...

"10"]

Ну и наконец, функция unlines разворачивает список строк в одну строку, вставляя символы перехода между строчками, в результате чего получается одна строка String:

"9\n7\n8\n8\n7\n10"

За счет чего функция получилась такой короткой? Критики функционального программирования наверняка скажут – да за счет использования большого количества стандартных функций. И это правда – но только отчасти. Стандартных функций на самом деле много, но они не являются специфическими, созданными только для одной задачи. Наоборот, функции map, show, length являются настолько общими, насколько это вообще возможно!

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