Outdated Notice
If you recall what we wrote about Expression-Oriented Programming (EOP) and the difference between expressions and statements, you’ll notice that in the previous lesson we used the for
keyword and foreach
method as tools for side effects. We used them to print the values in the collections to STDOUT using println
. Java has similar keywords, and many programmers used them for years without ever giving much thought to how they could be improved.
Once you start working with Scala you’ll see that in functional programming languages you can use more powerful “for
expressions” in addition to “for
loops.” In Scala, a for
expression — which we’ll write as 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 drop(1)
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.drop(1)
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)
A shorter version of the solution
We 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.drop(1).capitalize
You can also put curly braces around the algorithm, if you prefer:
val capNames = for (name <- names) yield { name.drop(1).capitalize }