Генераторы списков
Генератор списка — это запись «возьми каждый x из набора, отфильтруй и преобразуй» прямо как в школьной записи множеств.
[x*x | x <- [1..5]] читается как «квадрат x для каждого x из 1..5». Коротко, ясно и без единого цикла.
List comprehension (генератор списка) — компактный способ построить список из другого. Синтаксис вдохновлён математической записью множеств: { x² | x ∈ ℕ }.
squares = [x * x | x <- [1 .. 5]]
-- [1, 4, 9, 16, 25]
Читается справа налево по структуре: x <- [1..5] — «генератор», берущий каждый x из списка; x * x слева от | — что делать с каждым x.
[ x * x | x <- [1..5] ] | | выражение генератор «что вернуть» «откуда брать x»
Фильтры (условия)
После генератора можно добавить условия — берутся только подходящие элементы:
evens = [x | x <- [1 .. 20], even x]
-- [2,4,6,8,10,12,14,16,18,20]
bigEvens = [x | x <- [1 .. 20], even x, x > 10]
-- [12,14,16,18,20]
Несколько генераторов
Можно перебирать сразу несколько источников — получится комбинация всех пар:
pairs = [(x, y) | x <- [1, 2], y <- "ab"]
-- [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
-- пифагоровы тройки до 20
triples =
[ (a, b, c)
| c <- [1 .. 20], b <- [1 .. c], a <- [1 .. b]
, a*a + b*b == c*c ]
Это прямой потомок математической записи — и, кстати, Python позаимствовал точно такой же синтаксис:
# Та же идея на Python: list comprehensions
squares = [x * x for x in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
# с фильтром
evens = [x for x in range(1, 21) if x % 2 == 0]
print(evens)
# несколько генераторов
pairs = [(x, y) for x in [1, 2] for y in "ab"]
print(pairs) # [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
Декларативность вместо циклов
Генераторы списков — это, по сути, перенос математической записи множеств прямо в код, и в этом их обаяние. Вместо того чтобы описывать как обойти данные, вы описываете какой список хотите получить: «квадрат x для каждого чётного x из диапазона». Такой декларативный стиль читается как условие задачи, а не как инструкция исполнителю. Несколько генераторов дают декартово произведение — удобно для перебора пар и комбинаций, а добавленные условия фильтруют на лету. Главная дисциплина здесь — не перегружать одну строку: когда генераторов и условий становится много, выражение превращается в ребус, и логику лучше разнести или вынести в именованную функцию. Для простых же «отфильтровать и преобразовать» генератор почти всегда читается лучше ручной рекурсии и нередко короче связки map с filter.
Как это мыслить
Читайте генератор как предложение: «вернуть выражение для каждого генератора, при условии фильтра». Это декларативно: вы описываете, какой список хотите, а не как его собрать по шагам. Многие задачи, где так и тянет написать цикл, изящно умещаются в одну строку.
Интересно, что генераторы списков — это на самом деле синтаксический сахар над монадой списка, с которой вы познакомитесь в финальном разделе. Перебор нескольких источников и фильтрация — ровно то, что делает монадическая запись для списков под капотом. Поэтому, освоив генераторы сейчас, вы заодно готовите почву для понимания монад позже: интуиция «перебрать все варианты и отсеять неподходящие» окажется той же самой. Пока же достаточно воспринимать генератор как декларативный инструмент построения списков. Он покрывает огромную долю задач преобразования данных, читается как условие задачи и нередко оказывается и короче, и понятнее, чем эквивалентная связка map, filter и concat, особенно когда источников несколько.
Частые ошибки
- Путать порядок частей. Слева от
|— что возвращаем, справа — откуда берём и какие условия. - Перегружать одну строку. Много вложенных генераторов и условий лучше разнести или вынести в функцию.
- Забывать запятую между условиями. Генераторы и фильтры разделяются запятыми.
Best practices
- Используйте генераторы для простых «преобразовать и отфильтровать» — читается лучше рекурсии.
- Сложную логику внутри выражения выносите в именованную функцию.
- Если нужен только фильтр или только преобразование, иногда яснее
filter/map(следующий раздел).
Итог. Генераторы списков строят новые списки декларативно: [выражение | генератор, условие]. Они поддерживают фильтры и несколько источников, читаются как математика и часто заменяют циклы одной выразительной строкой.