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

Генератор списка — это запись «возьми каждый 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 (следующий раздел).

Итог. Генераторы списков строят новые списки декларативно: [выражение | генератор, условие]. Они поддерживают фильтры и несколько источников, читаются как математика и часто заменяют циклы одной выразительной строкой.

Проверьте себя
1. Что вернёт [x | x <- [1..10], odd x] ?
A[1,2,3,...,10]
BСписок нечётных чисел [1,3,5,7,9]
CСписок чётных чисел
DПустой список
2. Что находится слева от вертикальной черты | в генераторе списка?
AИсточник данных
BВыражение, описывающее, что попадёт в итоговый список
CУсловие фильтрации
DИмя функции