views:

119

answers:

3

The Scala language specification section 6.19 says:

A for comprehension for (p <- e) yield e0 is translated to e.map { case p => e0 }

So...

scala> val l : List[Either[String, Int]] = List(Left("Bad"), Right(1))
l: List[Either[String,Int]] = List(Left(Bad), Right(1))

scala> for (Left(x) <- l) yield x
res5: List[String] = List(Bad)

So far so good:

scala> l.map { case Left(x) => x }
<console>:13: warning: match is not exhaustive!
missing combination          Right

       l.map { case Left(x) => x }
             ^
scala.MatchError: Right(1)
    at $anonfun$1.apply(<console>:13)
    at ...

Why does the second version not work? Or rather, why does the first version work?

+4  A: 

If you use pattern matching in your for-comprehension the compiler will actually insert a call to filter with an instanceOf-check before applying the map.

EDIT:

Also in section 6.19 it says:

A generator p <- e followed by a guard if g is translated to a single generator p <- e.withFilter((x1, ..., xn) => g ) where x1, ..., xn are the free variables of p.

A generator is defined earlier on as:

Generator ::= Pattern1 ‘<-’ Expr [Guard]

When inspecting the bytecode you will see the call to filter preceding the call to map.

Moritz
Can you point to the bit of the specification that talk about this? I thought that `filter` only applied to **guards**: i.e. `for (e <- if cond)`
oxbow_lakes
6.19 For Comprehensions and For LoopsThe translation scheme is as follows. In a first step, every generator p <e,wherep is not irrefutable (§8.1) for the type of e is replaced byp <e.withFilter { case p => true; case _ => false }Then, the following rules are applied repeatedly until all comprehensions have beeneliminated.• A for comprehension for (p <e) yield e0 is translated toe.map { case p => e0 }.
Eastsun
+1  A: 

Scala 2.7 language specification, page 83, second paragraph from the bottom (don't have the 2.8 spec here). Inserting filters for generator pattern-matching is the first step in the for-comprehension translation process.

One caveat, the last time I checked, this doesn't work for typed patterns, which can be surprising. So in your example

for(x:Left <- l) yield x  

wouldn't work, throwing a type error

Dave Griffith
It can by tricked: val as = Seq("a", 1, true, ()); for (a @ (dummy: Boolean) <- as) yield a
retronym
+1  A: 

As an addition to Eastsun's remarks: Scala 2.8 has a method collect, which would work in your example:

l.collect { case Left(x) => x }
//--> List[String] = List(Bad)
Landei