Scala for Comprehensions: Complete Guide with Examples
Scala for Comprehensions: Complete Guide with Examples
Scala's for comprehensions are syntactic sugar for nested map, flatMap, and filter calls. They transform what would be deeply nested functional chains into clean, readable expressions. This module explains how they work and when to use them.
Basic for Loop vs for Comprehension
A plain for loop (without yield) is a side-effecting expression:
for (i <- 1 to 5) {
println(i)
}A for comprehension uses yield to produce a new collection:
val squares = for (i <- 1 to 5) yield i * i
println(squares) // Vector(1, 4, 9, 16, 25)The yield turns the for into an expression that returns a value instead of just running for side effects.
How for Comprehensions Desugar
The Scala compiler translates for comprehensions into method calls. Understanding this translation is key:
// for comprehension
val result = for {
x <- List(1, 2, 3)
y <- List(10, 20)
} yield x * y
// Desugars to:
val result2 = List(1, 2, 3).flatMap { x =>
List(10, 20).map { y =>
x * y
}
}
println(result) // List(10, 20, 20, 40, 30, 60)
println(result2) // List(10, 20, 20, 40, 30, 60)The rule: every generator except the last uses flatMap. The last generator uses map. Guards use withFilter.
Guards in for Comprehensions
Use if to add filters:
val evenSquares = for {
i <- 1 to 10
if i % 2 == 0
} yield i * i
println(evenSquares) // Vector(4, 16, 36, 64, 100)This desugars to:
(1 to 10).withFilter(_ % 2 == 0).map(i => i * i)for Comprehensions Over Option
The real power of for comprehensions shows when working with Option:
def findUser(id: Int): Option[String] = Map(1 -> "Alice", 2 -> "Bob").get(id)
def findEmail(name: String): Option[String] =
Map("Alice" -> "alice@example.com").get(name)
// Without for comprehension — nested flatMap
val email1: Option[String] = findUser(1).flatMap(name => findEmail(name))
// With for comprehension — much cleaner
val email2: Option[String] = for {
name <- findUser(1)
email <- findEmail(name)
} yield email
println(email2) // Some(alice@example.com)
// Short-circuits at the first None
val noEmail: Option[String] = for {
name <- findUser(99) // None — stops here
email <- findEmail(name)
} yield email
println(noEmail) // NoneIf any generator produces None, the entire comprehension returns None — no null checks, no if-else chains.
for Comprehensions Over Either
The same pattern works with Either:
def parseId(s: String): Either[String, Int] =
s.toIntOption.toRight(s"'$s' is not a valid ID")
def lookupUser(id: Int): Either[String, String] =
Map(1 -> "Alice").get(id).toRight(s"User $id not found")
def getEmailForIdString(idStr: String): Either[String, String] = for {
id <- parseId(idStr)
user <- lookupUser(id)
email <- findEmail(user).toRight(s"No email for $user")
} yield email
println(getEmailForIdString("1")) // Right(alice@example.com)
println(getEmailForIdString("abc")) // Left('abc' is not a valid ID)
println(getEmailForIdString("99")) // Left(User 99 not found)Variable Definitions in for Comprehensions
You can define intermediate values with = inside a for:
case class Order(id: Int, items: List[String], discount: Double)
val orders = List(
Order(1, List("apple", "banana"), 0.1),
Order(2, List("cherry"), 0.0)
)
val summaries = for {
order <- orders
itemCount = order.items.length
total = itemCount * 10.0 * (1 - order.discount)
} yield s"Order ${order.id}: $itemCount items, total $total"
summaries.foreach(println)
// Order 1: 2 items, total 18.0
// Order 2: 1 items, total 10.0= something in a for block is a value definition (not a generator) — it doesn't call flatMap or map, it just introduces a named value.
Nested for Comprehensions
for comprehensions can produce flat results from nested structures:
val matrix = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
val flattened = for {
row <- matrix
elem <- row
if elem % 2 != 0
} yield elem
println(flattened) // List(1, 3, 5, 7, 9)Frequently Asked Questions
Q: Is a Scala for comprehension the same as a for loop in other languages?
No — Scala's for with yield is an expression that produces a value, not just a loop. It's closer to list comprehensions in Python or LINQ in C#. Even without yield, the Scala for is translated into foreach calls rather than a native loop construct. The real insight is that for works with any type that has map, flatMap, and withFilter — not just collections, but also Option, Either, Future, and custom types.
Q: What does it mean that for comprehensions work on monads?
A monad is any type that provides flatMap (also called bind) and a way to wrap a value (like Some() or Right()). Scala's for comprehension is a general notation for sequencing monadic operations. When you chain Options in a for, you're using the Option monad. When you chain Futures, you're using the Future monad. The for comprehension gives you the same clean syntax regardless of the underlying type.
Q: What's the difference between = and <- in a for comprehension?
<- is a generator — it calls flatMap (or map for the last one) and binds the result. It "unwraps" the container. = is a value definition — it's equivalent to val, just available in the for scope. It doesn't call any methods and doesn't affect the sequencing. Use <- when you have an Option[A] or List[A] and want to bind the A. Use = when you want to name an intermediate calculation.
Part of Scala Mastery Course — Module 13 of 22.
