A grave misunderstanding
When I started using Scala, I was very confused by “for comprehensions” for a very long time. My mistake was to treat <-
as an “unwrapper”. For example, in the following, <-
“unwraps” the collection/container/monad, as expected.
def main(args: Array[String]): Unit = {
for { x <- Option(1) } { println(x) } // prints 1
for { x <- List(2) } { println(x) } // prints 2
for { x <- Try(3) } { println(x) } // prints 3
}
We also know that for comprehension is sugar for flatMap, map and withFilter1. So we can combine enumerators like the following to produce very succinct and elegant code.
val _: List[Int] = for {
x <- List(1)
y <- List(x + 1)
} yield y
// Is sugar for
val z: List[Int] = List(1).flatMap(x => List(x + 1).map(y => y))
So far so good. Sometimes, we want to do some computation over the enumerators:
def isOdd(x: Int): Option[Int] = if ((x % 2) == 0) None else Some(x)
val z: List[Int] = for {
x <- List(1)
y <- isOdd(x)
} yield y
This makes sense at first if we treat <-
as unwrapping the Option[Int]
. However, that is a wrong mental modal. If we try to replace Option
with Try
, we see that the code fails to compile.
def isOdd(x: Int): Try[Int] = Try(x + 1) // Note that this is now Try instead of Option
val z: List[Int] = for {
x <- List(1)
y <- isOdd(x) // This will fail to compile
} yield y
This took me by surprize for an embarassingly long time. I was confused as to why it was fine to use for comprehension for List
and Option
, but not List
and Try
. Similarly Try and Option Fails too2.
Recall that the type signature of flatMap is (or equivalently) F[A] => (A => F[B]) => F[B]
and that F
stays the same. So it makes sense that combining Try
and Option
wouldn’t work. However, why is it that List
and Option
works? It turns out that the implementation of flatMap
for List
takes a GenTraversableOnce
and an there is an implicit conversion from Option
to an Iterator
which satisfy that trait.
I think this is an example of some of the things people moving to Scala will be caught up by and probably should be emphasized by books introducing the language.
If a linguist were to look and tried to derive the syntactic rule, she’d be confused too. Let’s replace List, Option, and Try with the symbols A, B, C, and let combining them with “for comprehension” be the binary operator +. Then the observed rules are: A + A, B + B, C + C, A + B are fine, but A + C, B + C does not work. It is unclear why things break for C. ↩︎