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), а if — filter (точнее 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 особенно блистает.