for-выражения и yield

for-выражение в Scala выглядит как цикл, но на деле это элегантная сборка новой коллекции.

«for с yield — это не „повторяй действие“, а „собери результат из частей“.»

В Scala for — это не обычный цикл, а выразительный способ комбинировать коллекции. С ключевым словом yield он строит новую коллекцию из результатов.

val nums = List(1, 2, 3, 4)
val doubled = for n <- nums yield n * 2
println(doubled)   // List(2, 4, 6, 8)

Часть n <- nums — это генератор: n по очереди принимает каждое значение из nums. yield собирает результаты в новую коллекцию.

Фильтры внутри for

В for можно добавить условие — оно отсеет ненужные элементы.

val nums = List(1, 2, 3, 4, 5, 6)
val result = for
  n <- nums
  if n % 2 == 0     // фильтр
yield n * 10
println(result)   // List(20, 40, 60)

Несколько генераторов

Несколько генераторов дают вложенный перебор — как вложенные циклы, но компактнее.

val pairs = for
  x <- List(1, 2)
  y <- List("a", "b")
yield (x, y)
println(pairs)   // List((1,a), (1,b), (2,a), (2,b))

Это сахар над map/flatMap/filter

Важно понимать: for/yield — это синтаксический сахар. Компилятор переписывает его в map, flatMap и filter.

// эти две строки эквивалентны:
val a = for n <- nums yield n * 2
val b = nums.map(n => n * 2)

Та же идея на Python ▶

# for-выражение = генератор списка в Python
nums = [1, 2, 3, 4, 5, 6]
doubled = [n * 2 for n in nums]                 # for n <- nums yield n*2
result = [n * 10 for n in nums if n % 2 == 0]   # с фильтром

# несколько генераторов = вложенные for
pairs = [(x, y) for x in [1, 2] for y in ["a", "b"]]
print(doubled)
print(result)
print(pairs)   # [(1,'a'), (1,'b'), (2,'a'), (2,'b')]
for n <- nums  if n % 2 == 0  yield n * 10
    \_______/   \___________/    \_______/
    генератор      фильтр          результат
         |             |               |
      flatMap       filter            map

Как работает под капотом (JVM)

Компилятор не вводит никакой особой конструкции «for» в байт-код. Он механически переписывает выражение: каждый генератор после первого становится flatMap, последний — map (если есть yield), а iffilter (точнее withFilter для ленивости). Поэтому for работает с любым типом, у которого есть map и flatMap — не только с коллекциями, но и с Option, и с Future. Это делает for универсальным инструментом.

Частые ошибки

  • Забыть yield. Без него for ничего не собирает и возвращает Unit — получится просто цикл с побочными эффектами.
  • Путать <- и =. <- — генератор из коллекции, = — определение промежуточного значения.
  • Ожидать порядок не тот. При нескольких генераторах внешний меняется медленнее, внутренний — быстрее.

Best practices

  • Используйте for/yield там, где вложенные map/flatMap становятся нечитаемыми.
  • Помните, что for работает не только с коллекциями, но и с Option, Future и др.
  • Не забывайте yield, если нужен результат, а не побочный эффект.

Один синтаксис для коллекций, Option и Future

Истинная ценность for-выражений раскрывается, когда понимаешь их универсальность. Поскольку компилятор переписывает for в map, flatMap и filter, эта конструкция работает с любым типом, у которого есть эти методы. Это значит, что один и тот же знакомый синтаксис применим к коллекциям, к Option, к Either, к асинхронным Future и ко многим библиотечным типам.

Это объединение огромно для обучения: освоив for на списках, вы бесплатно получаете умение работать с цепочками вычислений, которые могут отсутствовать или дать ошибку. В следующих уроках вы увидите, как одно и то же for-выражение элегантно соединяет несколько Option или Either, автоматически прерываясь на первой пустоте или ошибке. Это превращает for из «цикла» в общий механизм композиции вычислений.

Стоит потренироваться переводить for-выражения в эквивалентные цепочки map и flatMap и обратно. Это упражнение проясняет, что происходит под капотом, и помогает выбрать более читаемую форму для каждого случая. Простые преобразования часто яснее как прямой map, а вложенные комбинации нескольких источников — почти всегда нагляднее как for.

Итоги. for/yield — выразительный сахар над map/flatMap/filter, удобный для генераторов и фильтров. Дальше — Option, где for особенно блистает.

Проверьте себя
1. Во что компилятор переписывает for-выражение с yield?
AВ обычный цикл while
BВ вызовы map, flatMap и filter
CВ рекурсию
DВ SQL-запрос
2. Что произойдёт, если в for забыть yield?
AСоберётся коллекция результатов
BПолучится цикл ради побочных эффектов с типом Unit, без сбора результата
CОшибка компиляции всегда
DВернётся первый элемент