How to Scala, Written by a Java: 3 – for

(Find these examples at my GitHub)

In the imperative world, for is a loop. It lists statements to be executed on each item in a declared item holder. In Scala, for is not a loop. It is an expression which describes how to transform item holders. The for expression comes in two broad flavors. The first flavor acts like the classical for statement; it returns nothing. The second flavor acts as an expression and returns data.

  val list1: List[Int] = List.range(1, 101)
  val list2: List[Int] = List.range(-100, 0)</p>
<p>  val mutableList = scala.collection.mutable.ListBuffer[Int]()
  for(i <- list1){
    for(j <- list2){
      if(0 == i + j){
        mutableList += (i - j)
      }
    }
  }

This code crates two List objects, one holding the integers from 1 to 100, the other holding integers from -100 to -1. It creates a list of even numbers from 2 through 200. The following code performs the same operations using the Scala Collections API.

  val collectionApiList =
    list1.
    flatMap{i =>
      list2.filter{j =>
        0 == i + j
      }.
      map{j =>
        i - j
      }
    }

The Scala Collections API is well documented in the above link. This post focuses on for comprehensions so, we’ll only cover the portions of the Scala Collections API necessary to understand them fully.

1. Collection Basics

For comprehensions are syntactic sugar. They help write code that is more easily reasoned about than the standard collection manipulation techniques. Three methods are most notably exploited here: map, flatMap, filter.

1.1 Collection map

The map method on collections maps elements to other values and returns a transformed collection.

  val all1To10 = List.range(1, 11)
  val even2To20 =
    all1To10.
    map{item =>
      2 * item
    }

This code, for example, creates a list of integers from 1 to 10, and maps those elements to their doubles creating a list of even integers from 2 to 20. For the more academically inclined, if you take collections to be categories, map is a functor from one collection to another.

1.2 Collection flatMap

The flatMap method on collections maps elements to collections of other values and returns a single collection of the values within the collections mapped. This is different from map as a map from item to collections will return a collection of collections rather than a collection of all the elements from each collection.

  val rangesOf10_1To100 =
    all1To10.
    map{item =>
      val base = (10 * (item - 1)) + 1
      List.range(base, base + 10)
    }
  val all1To100 =
    all1To10.
    flatMap{item =>
      val base = (10 * (item - 1)) + 1
      List.range(base, base + 10)
    }

The first expression produces a List of Lists the first from 1 to 10, the second from 11 to 20 and do on until the last from 91 to 100. The second expression produces a List from 1 to 100. flatMap like map can be seen as a functor on collections.

1.3 Collection filter

Filter produces a new collection by filtering (as its name suggests) the elements of the original collection.

  val even2To10 =
    all1To10.
    filter{item =>
      0 == item % 2
    }

This produces a List of even Integers from 2 to 10 from the List of Integers from 1 to 10 by applying the defined filter function.

2 The for comprehension

Going back to our original example, we show it as a for comprehension.

  val comprehendedList =
    for{
      i <- list1
      j <- list2
      if 0 == i + j//The if statements are called guards
    }yield{
      i - j
    }

This is equivalent to the combination of map, flatMap and filter shown above. In fact, this is merely syntactic sugar provided by the Scala compiler for those calls. We could just as simply provide a list of multiples of 4 from 2 to 200:

  val comprehendedListBy4_1 =
    for{
      i <- list1 if 0 == i % 2
      j <- list2 if 0 == i + j
    }yield{
      i - j
    }
  val comprehendedListBy4_2 =
    for{
      i <- list1
      if 0 == i % 2
      j <- list2
      if 0 == i + j
    }yield{
      i - j
    }
  val comprehendedListBy4_3 =
    for{
      i <- list1
      j <- list2
      if 0 == i % 2
      if 0 == i + j
    }yield{
      i - j
    }
  val comprehendedListBy4_4 =
    for{
      i <- list1
      j <- list2
      if(0 == i % 2)
      if(0 == i + j)
    }yield{
      i - j
    }

All four of these expressions produce the same results: a List of Integer multiples of 4 from 4 to 200. In Scala, collections are a good example of what’s called a monad. Monads are basically a container with special methods which allow the container to create more of itself. Collection map, flatMap and filter are some of these special functions. Monads will be covered another time.