Scala for comprehensions and partial map
-
30-09-2020 - |
Вопрос
The Scala language specification section 6.19 says:
A for comprehension
for (p <- e) yield e0
is translated toe.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?
Решение
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
.
Другие советы
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)
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