For Comprehensions

<div class="callout callout-info"> In addition to the standard imports given at the start of the chapter, in this section we're assuming the following:

import doodle.random._

</div>

Scala provides some special syntax, called a for comprehension, that makes it simpler to write long sequences of flatMap and map.

For example, the code for randomConcentricCircles has a call to flatMap and map.

def randomConcentricCircles(count: Int, size: Int): Random[Image] =
  count match {
    case 0 => Random.always(Image.empty)
    case n => 
      randomCircle(size, randomPastel).flatMap{ circle =>
        randomConcentricCircles(n-1, size + 5).map{ circles =>
          circle.on(circles)
        }
      }
  }

This can be replaced with a for comprehension.

def randomConcentricCircles(count: Int, size: Int): Random[Image] =
  count match {
    case 0 => Random.always(Image.empty)
    case n => 
      for {
        circle  <- randomCircle(size, randomPastel) 
        circles <- randomConcentricCircles(n-1, size + 5)
      } yield circle.on(circles) 
  }

The for comprehension is often easier to read than direct use of flatMap and map.

A general for comprehension

for {
  x <- a
  y <- b
  z <- c
} yield e

translates to:

a.flatMap(x => b.flatMap(y => c.map(z => e)))

Which is to say that every <-, except the last, turns into a flatMap, and the last <- becomes a map.

For comprehensions are translated by the compiler into uses of flatMap and map. There is no magic going on. It is just a different way of writing code that would use flatMap and map that avoids excessive nesting.

Note that the for comprehension syntax is more flexible than what we have presented here. For example, you can drop the yield keyword from a for comprehension and the code will still compile. It just won't return a result. We're not going to use any of these extensions in Creative Scala, however.

Exploring Random→