How to create a Scala “for comprehension” (for/yield loop)

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 3.4, “How to create a ‘for comprehension’ (for/yield loop).”

Problem

You want to create a new Scala collection from an existing collection by applying an algorithm (and potentially one or more guards) to each element in the original collection.

Solution

Use a yield statement with a for loop and your algorithm to create a new collection from an existing collection.

For instance, given an array of lowercase strings:

scala> val names = Array("chris", "ed", "maurice")
names: Array[String] = Array(chris, ed, maurice)

you can create a new array of capitalized strings by combining yield with a for loop and a simple algorithm:

scala> val capNames = for (e <- names) yield e.capitalize
capNames: Array[String] = Array(Chris, Ed, Maurice)

Using a for loop with a yield statement is known as a for-comprehension. If your algorithm requires multiple lines of code, perform the work in a block after the yield keyword:

scala> val lengths = for (e <- names) yield {
     |     // imagine that this required multiple lines of code
     |     e.length
     | }
lengths: Array[Int] = Array(5, 2, 7)

Except for rare occasions, the collection type returned by a for comprehension is the same type that you begin with. For instance, if the collection you’re looping over is an ArrayBuffer:

var fruits = scala.collection.mutable.ArrayBuffer[String]()
fruits += "apple"
fruits += "banana"
fruits += "orange"

the collection your loop returns will also be an ArrayBuffer:

scala> val out = for (e <- fruits) yield e.toUpperCase
out: scala.collection.mutable.ArrayBuffer[java.lang.String] = ArrayBuffer(APPLE, BANANA, ORANGE)

If your input collection is a List, the for/yield loop will return a List:

scala> val fruits = "apple" :: "banana" :: "orange" :: Nil
fruits: List[java.lang.String] = List(apple, banana, orange)

scala> val out = for (e <- fruits) yield e.toUpperCase
out: List[java.lang.String] = List(APPLE, BANANA, ORANGE)

Discussion

If you’re new to using yield with a for loop, it can help to think of the loop like this:

  • When it begins running, the for/yield loop immediately creates a new, empty collection that is of the same type as the input collection. For example, if the input type is a Vector, the output type will also be a Vector. You can think of this new collection as being like a bucket.
  • On each iteration of the for loop, a new output element is created from the current element of the input collection. When the output element is created, it’s placed in the bucket.
  • When the loop finishes running, the entire contents of the bucket are returned.

That’s a simplification of the process, but I find it helpful when explaining the process. Writing a basic for/yield expression without a guard is just like calling the map method on a collection. For instance, the following for comprehension converts all the strings in the fruits collection to uppercase:

scala> val out = for (e <- fruits) yield e.toUpperCase
out: List[String] = List(APPLE, BANANA, ORANGE)

Calling the map method on the collection does the same thing:

scala> val out = fruits.map(_.toUpperCase)
out: List[String] = List(APPLE, BANANA, ORANGE)

When I first started learning Scala, I wrote all of my code using for/yield expressions until the map light bulb went on one day.

See Also