For expressions in Scala may look similar to for loops in Java or other imperative languages, but they work closer to list comprehensions in Haskell or Python. One of the main differences is that a for loop in Java usually has side effects, i.e., it mutates something outside the loop. A for expression in Scala returns a Sequence
instead.
Let's try a simple problem to understand how for expressions are useful. Imagine that we want to find all the prime numbers between 1
and a given number n
.
With imperative for loops we could do something like this:
const primesTo = n => {
const primes = []
for (let i = 1; i <= n; i++) {
if (isPrime(i)) primes.push(i)
}
return primes
}
We have an index that we increment until we reach the value n
, and for each iteration we check if the number is prime and, in that case, we push it to an array. Observe that we are effectively mutating a value that's outside the scope of the for loop, the array primes
.
In functional programming we could instead generate a sequence of numbers between 1
and n
and then filter over the sequence to keep the numbers that are prime.
def primesTo (n: Int): Seq[Int] =
(1 to n) filter isPrime
It is not relevant for this topic, but here's my naive implementation of isPrime
in Scala.
def isPrime (n: Int): Boolean =
(2 until n) forall (n % _ != 0)
The for expression in Scala takes one or more generators and, optionally, filters. A generator takes the form of x <- <collection>
and a filter takes the form of if <condition>
. So let's rewrite our primesTo
function with a for expression.
def primesTo (n: Int): Seq[Int] =
for (i <- (1 to n) if isPrime(i)) yield i
Not a big difference so far. One could even argue that it's longer than the previous example. However, for expressions can make the code more readable when we enter the realm of nested sequences.
Let's try with another problem. What if we want to find all combinations of three integers smaller than n
that fulfill the Pythagorean theorem?
With imperative for loops we could do something like this:
const pythagorean = n => {
const result = []
for (let a = 1; a < n; a++) {
for (let b = 1; b < a; b++) {
for (let c = 1; c < b; c++) {
if (a * a === b * b + c * c) {
result.push([ a, b, c ])
}
}
}
}
return result
}
And if we try to generate a sequence and filter over it in Scala we could do something similar to this:
def pythagorean (n: Int): Seq[(Int, Int, Int)] =
(1 until n) flatMap (a =>
(1 until a) flatMap (b =>
(1 until b) map (c => (a, b, c))
)
) filter (v => v._1 * v._1 == v._2 * v._2 + v._3 * v._3)
It's not entirely horrible, but at this point it's not really the most readable code ever.
In for expressions we can use multiple generators, so we don't need as many indentation levels as in the previous examples.
def pythagorean (n: Int): Seq[(Int, Int, Int)] =
for {
a <- (1 until n)
b <- (1 until a)
c <- (1 until b)
if a * a == b * b + c * c
} yield (a, b, c)
Notice that in this example we added the generators and filters between {}
instead of ()
. The difference between these two is that with parenthesis we need to add a semicolon between different generators, while with curly brackets we can skip them, which is convenient when we are separating the generators with new lines.
In summary, for expressions provide a more readable syntax to work with nested sequences.
Top comments (0)