Scala for Expressions

If you recall what I wrote about Expression-Oriented Programming (EOP) and the difference between expressions and statements, you’ll notice that in the previous lesson I used the for keyword and foreach method as tools for side effects. I used them to print the values in the collections to STDOUT using println. Java has similar keywords, and that’s how I used them for many years without ever giving much thought to how they could be improved.

After I started working with Scala I learned that in functional programming languages you can use more powerful “for expressions” in addition to “for loops.” In Scala, a for expression is a different use of the for construct. While a for-loop is used for side effects (such as printing output), a for-expression is used to create new collections from existing collections.

For example, given this list of integers:

val nums = Seq(1,2,3)

You can create a new list of integers where all of the values are doubled, like this:

val doubledNums = for (n <- nums) yield n * 2

That expression can be read as, “For every number n in the list of numbers nums, double each value, and then assign all of the new values to the variable doubledNums.” This is what it looks like in the Scala REPL:

scala> val doubledNums = for (n <- nums) yield n * 2
doubledNums: Seq[Int] = List(2, 4, 6)

As the REPL output shows, the new list doubledNums contains these values:

List(2,4,6)

In summary, the result of the for-expression is that it creates a new variable named doubledNums whose values were created by doubling each value in the original list, nums.

Capitalizing a list of strings

You can use the same approach with a list of strings. For example, given this list of lowercase strings:

val names = List("adam", "david", "frank")

You can create a list of capitalized strings with this for-expression:

val ucNames = for (name <- names) yield name.capitalize

The REPL shows how this works:

scala> val ucNames = for (name <- names) yield name.capitalize
ucNames: List[String] = List(Adam, David, Frank)

Success! Each name in the new variable ucNames is capitalized.

The yield keyword

Notice that both of those for-expressions use the yield keyword:

val doubledNums = for (n <- nums) yield n * 2
                                  -----

val ucNames = for (name <- names) yield name.capitalize
                                  -----

Using yield after for is the “secret sauce” that says, “I want to yield a new collection from the existing collection that I’m iterating over in the for-expression, using the algorithm shown.”

Using a block of code after yield

The code after the yield expression can be as long as necessary to solve the current problem. For example, given a list of strings like this:

val names = List("_adam", "_david", "_frank")

Imagine that you want to create a new list that has the capitalized names of each person. To do that, you first need to remove the underscore character at the beginning of each name, and then capitalize each name. To remove the underscore from each name, you call the tail method on each String. After you do that, you call the capitalize method on each string. Here’s how you can use a for-expression to solve this problem:

val capNames = for (name <- names) yield {
    val nameWithoutUnderscore = name.tail
    val capName = nameWithoutUnderscore.capitalize
    capName
}

If you put that code in the REPL, you’ll see this result:

capNames: List[String] = List(Adam, David, Frank)

How tail works

The tail method works on sequential collections, and returns every element in the collection other than the first element (which is known as the “head” element). Because a String is also a linear collection, tail works on strings like this:

scala> val result = "fred".tail
result: String = red

A shorter version of the solution

I show the verbose form of the solution in that example so you can see how to use multiple lines of code after yield. However, for this particular example you can also write the code like this, which is more of the Scala style:

val capNames = for (name <- names) yield name.tail.capitalize

You can also put curly braces around the algorithm, if you prefer:

val capNames = for (name <- names) yield { name.tail.capitalize }

See also

results matching ""

    No results matching ""