This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 3.4, “How to create a ‘for comprehension’ (for/yield loop).”
Problem
You want to create a new Scala collection from an existing collection by applying an algorithm (and potentially one or more guards) to each element in the original collection.
Solution
Use a yield
statement with a for
loop and your algorithm to create a new collection from an existing collection.
For instance, given an array of lowercase strings:
scala> val names = Array("chris", "ed", "maurice") names: Array[String] = Array(chris, ed, maurice)
you can create a new array of capitalized strings by combining yield
with a for
loop and a simple algorithm:
scala> val capNames = for (e <- names) yield e.capitalize capNames: Array[String] = Array(Chris, Ed, Maurice)
Using a for
loop with a yield
statement is known as a for-comprehension. If your algorithm requires multiple lines of code, perform the work in a block after the yield
keyword:
scala> val lengths = for (e <- names) yield { | // imagine that this required multiple lines of code | e.length | } lengths: Array[Int] = Array(5, 2, 7)
Except for rare occasions, the collection type returned by a for comprehension is the same type that you begin with. For instance, if the collection you’re looping over is an ArrayBuffer
:
var fruits = scala.collection.mutable.ArrayBuffer[String]() fruits += "apple" fruits += "banana" fruits += "orange"
the collection your loop returns will also be an ArrayBuffer
:
scala> val out = for (e <- fruits) yield e.toUpperCase out: scala.collection.mutable.ArrayBuffer[java.lang.String] = ArrayBuffer(APPLE, BANANA, ORANGE)
If your input collection is a List
, the for/yield loop will return a List:
scala> val fruits = "apple" :: "banana" :: "orange" :: Nil fruits: List[java.lang.String] = List(apple, banana, orange) scala> val out = for (e <- fruits) yield e.toUpperCase out: List[java.lang.String] = List(APPLE, BANANA, ORANGE)
Discussion
If you’re new to using yield
with a for
loop, it can help to think of the loop like this:
- When it begins running, the for/yield loop immediately creates a new, empty collection that is of the same type as the input collection. For example, if the input type is a
Vector
, the output type will also be aVector
. You can think of this new collection as being like a bucket. - On each iteration of the
for
loop, a new output element is created from the current element of the input collection. When the output element is created, it’s placed in the bucket. - When the loop finishes running, the entire contents of the bucket are returned.
That’s a simplification of the process, but I find it helpful when explaining the process. Writing a basic for/yield expression without a guard is just like calling the map
method on a collection. For instance, the following for comprehension converts all the strings in the fruits collection to uppercase:
scala> val out = for (e <- fruits) yield e.toUpperCase out: List[String] = List(APPLE, BANANA, ORANGE)
Calling the map
method on the collection does the same thing:
scala> val out = fruits.map(_.toUpperCase) out: List[String] = List(APPLE, BANANA, ORANGE)
When I first started learning Scala, I wrote all of my code using for/yield expressions until the map
light bulb went on one day.
See Also
- Comparisons between for-comprehensions and
map
are shown in more detail in Recipe 10.13, “How to transform one Scala collection to another with for/yield” and Recipe 10.14, “How to transform one Scala collection to another with map”. - The official Scala website offers an introduction to sequence comprehensions
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |