Выбрать главу

ghci> [0.1, 0.3 .. 1]

[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]

Мой совет: не используйте такие числа в интервалах!

Интервалы, кроме прочего, можно использовать для создания бесконечных списков, просто не указывая верхний предел. Позже мы рассмотрим этот вариант в подробностях. А сейчас давайте посмотрим, как можно получить список первых 24 чисел, кратных 13. Конечно, вы могли бы написать [13,26..24*13]. Но есть способ получше: take 24 [13,26..]. Поскольку язык Haskell ленив, он не будет пытаться немедленно вычислить бесконечный список, потому что процесс никогда не завершится. Он подождёт, пока вы не захотите получить что-либо из такого списка. Тут-то обнаружится, что вы хотите получить только первые 24 элемента, что и будет исполнено.

Немного функций, производящих бесконечные списки:

• Функция cycle принимает список и зацикливает его в бесконечный. Если вы попробуете отобразить результат, на это уйдёт целая вечность, поэтому вам придётся где-то его обрезать.

ghci> take 10 (cycle [1,2,3])

[1,2,3,1,2,3,1,2,3,1]

ghci> take 12 (cycle "LOL ")

"LOL LOL LOL "

• Функция repeat принимает элемент и возвращает бесконечный список, состоящий только из этого элемента. Это подобно тому, как если бы вы зациклили список из одного элемента.

ghci> take 10 (repeat 5)

[5,5,5,5,5,5,5,5,5,5]

Однако проще использовать функцию replicate, если вам нужен список из некоторого количества одинаковых элементов. replicate 3 10 вернёт [10,10,10].

Генераторы списков

Если вы изучали курс математики, то, возможно, сталкивались со способом задания множества путём описания характерных свойств, которыми должны обладать его элементы. Обычно этот метод используется для построения подмножеств из множеств.

Вот пример простого описания множества. Множество, состоящее из первых десяти чётных чисел, это S = {2 · x | x ∈ N, x ≤ 10}, где выражение перед символом | называется производящей функцией (output function), x – переменная, N – входной набор, а x ≤ 10 – условие выборки. Это означает, что множество содержит удвоенные натуральные числа, которые удовлетворяют условию выборки.

Если бы нам потребовалось написать то же самое на языке Haskell, можно было бы изобрести что-то вроде: take 10 [2,4..]. Но что если мы хотим не просто получить первые десять удвоенных натуральных чисел, а применить к ним некую более сложную функцию? Для этого можно использовать генератор списков. Он очень похож на описание множеств:

ghci> [x*2 | x <– [1..10]]

[2,4,6,8,10,12,14,16,18,20]

В выражении [x*2 | x <– [1..10]] мы извлекаем элементы из списка [1..10], т. е. x последовательно принимает все значения элементов списка. Иногда говорят, что x связывается с каждым элементом списка. Часть генератора, находящаяся левее вертикальной черты |, определяет значения элементов результирующего списка. В нашем примере значения x, извлечённые из списка [1..10], умножаются на два.

Теперь давайте добавим к этому генератору условие выборки (предикат). Условия идут после задания источника данных и отделяются от него запятой. Предположим, что нам нужны только те элементы, которые, будучи удвоенными, больше либо равны 12.

ghci> [x*2 | x <– [1..10], x*2 >= 12]

[12,14,16,18,20]

Это работает. Замечательно! А как насчёт ситуации, когда требуется получить все числа от 50 до 100, остаток от деления на 7 которых равен 3? Легко!

ghci> [ x | x <– [50..100], x `mod` 7 == 3]

[52,59,66,73,80,87,94]

И снова получилось!

ПРИМЕЧАНИЕ. Заметим, что прореживание списков с помощью условий выборки также называется фильтрацией.

Мы взяли список чисел и отфильтровали их условиями. Теперь другой пример. Давайте предположим, что нам нужно выражение, которое заменяет каждое нечётное число больше 10 на БАХ!", а каждое нечётное число меньше 10 – на БУМ!". Если число чётное, мы выбрасываем его из нашего списка. Для удобства поместим выражение в функцию, чтобы потом легко использовать его повторно.

boomBangs xs = [if x < 10 then "БУМ!" else "БАХ!" | x <– xs, odd x]

ПРИМЕЧАНИЕ. Помните, что если вы пытаетесь определить эту функцию в GHCi, то перед её именем нужно написать let. Если же вы описываете её в отдельном файле, а потом загружаете его в GHCi, то никакого let не требуется.