For-Comprehensions
Prerequisites: Higher-order functions, Option: no more null
A for ... yield expression is syntax sugar for map, flatMap,
and withFilter. That means it works with any type that has those
methods — not just collections.
What it expands to
@main def desugar(): Unit =
val a = for
x <- List(1, 2)
y <- List("a", "b")
yield s"$x$y"
val b = List(1, 2).flatMap(x => List("a", "b").map(y => s"$x$y"))
println(a)
println(a == b)
List(1a, 1b, 2a, 2b)
true
Every <- except the last becomes flatMap; the last becomes map.
With Option
case class User(id: Int, name: String, managerId: Option[Int])
val users = Map(
1 -> User(1, "Grace", None),
2 -> User(2, "Ada", Some(1)),
3 -> User(3, "Linus", Some(9))
)
def managerName(userId: Int): Option[String] =
for
user <- users.get(userId)
managerId <- user.managerId
manager <- users.get(managerId)
yield manager.name
@main def options(): Unit =
println(managerName(2))
println(managerName(1))
println(managerName(3))
println(managerName(7))
Some(Grace)
None
None
None
Each step may produce None; the comprehension stops at the first None.
Written with nested if checks, this would be much longer.
Values and filters inside
@main def extras(): Unit =
val result = for
n <- 1 to 20
square = n * n
if square % 3 == 0
yield s"$n^2=$square"
println(result.mkString(", "))
3^2=9, 6^2=36, 9^2=81, 12^2=144, 15^2=225, 18^2=324
name = expression defines an intermediate value; if filters.
The same syntax works with Either, Try, and Future (upcoming lessons),
and with effect types like Cats Effect's IO — one reason
for-comprehensions are everywhere in Scala code.