Как вы видите, символ .
используется для обращения к функциям, импортированным из модулей с указанием квалификатора, например: M.filter
. Мы также помним, что он используется для обозначения композиции функций. Как Haskell узнаёт, что мы имеем в виду? Если мы помещаем символ .
между квалифицированным именем модуля и функцией без пробелов – это обращение к функции из модуля; во всех остальных случаях – композиция функций.
ПРИМЕЧАНИЕ. Отличный способ узнать Haskell изнутри – просмотреть документацию к стандартной библиотеке и исследовать все стандартные модули и их функции. Также можно изучить исходные тексты всех модулей. Чтение исходных текстов некоторых модулей – отличный способ освоить язык и прочувствовать его особенности[9].
Решение задач средствами стандартных модулей
Модули стандартной библиотеки содержат массу функций, способных облегчить программирование на языке Haskell. Познакомимся с некоторыми из них, решая конкретные задачи.
Подсчёт слов
Предположим, что у нас имеется строка, содержащая много слов. Мы хотим выяснить, сколько раз в этой строке встречается каждое слово. Первой функцией, которую мы применим, будет функция words
из модуля Data.List
. Эта функция преобразует строку в список строк, в котором каждая строка представляет одно слово из исходной строки. Небольшой пример:
ghci> words "всё это слова в этом предложении"
["всё","это","слова","в","этом","предложении"]
ghci> words "всё это слова в этом предложении"
["всё","это","слова","в","этом","предложении"]
Затем воспользуемся функцией group
, которая тоже «живёт» в Data.List
, чтобы сгруппировать одинаковые слова. Эта функция принимает список и собирает одинаковые подряд идущие элементы в подсписки:
ghci> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
[[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]
Но что если одинаковые элементы идут в списке не подряд?
ghci> group ["бум","бип","бип","бум","бум"]
[["бум"],["бип","бип"],["бум","бум"]]
Получаем два списка, содержащих "бум"
, тогда как нам бы хотелось, чтобы все вхождения одного и того же слова попали в один список. Что делать? Мы можем предварительно отсортировать список! Для этого применим функцию sort
из Data.List
. Она принимает список элементов, которые могут быть упорядочены, и возвращает новый список, содержащий те же элементы, но упорядоченные от наименьшего к наибольшему:
ghci> sort [5,4,3,7,2,1]
[1,2,3,4,5,7]
ghci> sort ["бум","бип","бип","бум","бум"]
["бип","бип","бум","бум","бум"]
Заметим, что строки упорядочены по алфавиту.
Теперь всё необходимое у нас есть, осталось только записать решение. Берём строку, разбиваем её на список слов, сортируем слова и группируем одинаковые. Затем применяем map
и получаем список вроде ("boom", 3)
; это означает, что слово "boom"
встретилось в исходной строке трижды.
import Data.List
wordNums :: String -> [(String, Int)]
wordNums = map (\ws -> (head ws, length ws)) . group . sort . words
Для написания этой функции мы применили композицию функций. Предположим, что мы вызвали функцию wordNums
для строки "уа
уа
уи
уа"
. К этой строке применяется функция words
, результатом которой будет список ["уа","уа","уи","уа"]
. После его сортировки функцией sort
получим новый список ["уа","уа","уа","уи"]
. Группировка одинаковых подряд идущих слов функцией group
даст нам список [["уа","уа","уа"],["уи"]]
. Затем с помощью функции map
к каждому элементу такого списка (то есть к подсписку) будет применена анонимная функция, которая превращает список в пару – «голова» списка, длина списка. В конечном счёте получаем [("уа",3),("уи",1)]
.
Вот как можно написать ту же функцию, не пользуясь операцией композиции:
wordNums xs = map (\ws -> (head ws, length ws)) (group (sort (words xs)))
Кажется, здесь избыток скобок! Думаю, нетрудно заметить, насколько более читаемой делает функцию операция композиции.