Tour of Scala

For Comprehensions

Language

Scala offers a lightweight notation for expressing sequence comprehensions. Comprehensions have the form for (enumerators) yield e, where enumerators refers to a semicolon-separated list of enumerators. An enumerator is either a generator which introduces new variables, or it is a filter. A comprehension evaluates the body e for each binding generated by the enumerators and returns a sequence of these values.

Here’s an example:

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

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

val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30))
  yield user.name  // i.e. add this to a list

twentySomethings.foreach(name => println(name))  // prints Travis Dennis

The for loop used with a yield statement actually creates a List. Because we said yield user.name, it’s a List[String]. user <- userBase is our generator and if (user.age >=20 && user.age < 30) is a guard that filters out users who are in their 20s.

Here is a more complicated example using two generators. It computes all pairs of numbers between 0 and n-1 whose sum is equal to a given value v:

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

foo(10, 10) foreach {
  case (i, j) =>
    print(s"($i, $j) ")  // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5)
}

Here n == 10 and v == 10. On the first iteration, i == 0 and j == 0 so i + j != v and therefore nothing is yielded. j gets incremented 9 more times before i gets incremented to 1. Without the if guard, this would simply print the following:


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

Note that comprehensions are not restricted to lists. Every datatype that supports the operations withFilter, map, and flatMap (with the proper types) can be used in sequence comprehensions.

You can omit yield in a comprehension. In that case, comprehension will return Unit. This can be useful in case you need to perform side-effects. Here’s a program equivalent to the previous one, but without using yield:

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

foo(10, 10)