Scala предлагает простую запись для выражения последовательных преобразований. Эти преобразования можно упростить используя специальный синтаксис for выражения (for comprehension), который записывается как for (enumerators) yield e, где enumerators относятся к списку перечислителей, разделенных точкой с запятой. Где отдельный такой “перечислитель” (enumerator) является либо генератором, который вводит новые переменные, либо фильтром. For-выражение вычисляет тело e (которое связанно с тем что генерирует enumerator) и возвращает последовательность вычислений.

Вот пример:

case class User(name: String, age: Int)

val userBase = List(
  User("Travis", 28),
  User("Kelly", 33),
  User("Jennifer", 44),
  User("Dennis", 23))

val twentySomethings =
  for (user <- userBase if user.age >=20 && user.age < 30)
  yield user.name  // т. е. добавить результат к списку

twentySomethings.foreach(println)  // выводит "Travis Dennis"
case class User(name: String, age: Int)

val userBase = List(
  User("Travis", 28),
  User("Kelly", 33),
  User("Jennifer", 44),
  User("Dennis", 23))

val twentySomethings =
  for user <- userBase if user.age >=20 && user.age < 30
  yield user.name  // т. е. добавить результат к списку

twentySomethings.foreach(println)  // выводит "Travis Dennis"

for-выражение, используется с оператором yield, на самом деле создает List. Потому что мы указали yield user.name (то есть вывести имя пользователя), получаем List[String]. user <- userBase и есть наш генератор, а if (user.age >=20 && user.age < 30) - это фильтр который отфильтровывает пользователей, не достигших 30-летнего возраста.

Ниже приведен более сложный пример использования двух генераторов. Он вычисляет все пары чисел между 0 и n-1, сумма которых равна заданному значению v:

def foo(n: Int, v: Int) =
   for (i <- 0 until n;
        j <- 0 until n if i + j == v)
   yield (i, j)

foo(10, 10).foreach {
  case (i, j) =>
    println(s"($i, $j) ")  // выводит (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1)
}
def foo(n: Int, v: Int) =
   for i <- 0 until n
       j <- 0 until n if i + j == v
   yield (i, j)

foo(10, 10).foreach {
  (i, j) => println(s"($i, $j) ")  // выводит (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1)
}

Здесь n == 10 и v == 10. На первой итерации i == 0 и j == 0 так i + j != v и поэтому ничего не выдается. j увеличивается еще в 9 раз, прежде чем i увеличивается до 1. Без фильтра if будет просто напечатано следующее:

(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 0) ...

Обратите внимание, что for-выражение не ограничивается только работой со списками. Каждый тип данных, поддерживающий операции withFilter, map, and flatMap (с соответствующими типами), может быть использован в for-выражении.

Вы можете обойтись без yield в for-выражении. В таком случае, результатом будет Unit. Это может быть полезным для выполнения кода основанного на побочных эффектах. Вот программа, эквивалентная предыдущей, но без использования yield:

def foo(n: Int, v: Int) =
   for (i <- 0 until n;
        j <- 0 until n if i + j == v)
   println(s"($i, $j)")

foo(10, 10)
def foo(n: Int, v: Int) =
   for i <- 0 until n
       j <- 0 until n if i + j == v
   do println(s"($i, $j)")

foo(10, 10)

Дополнительные ресурсы

Contributors to this page: