Search code examples
scalafor-looptype-mismatchfor-comprehensionscala-option

Composing Option with List in for-comprehension gives type mismatch depending on order


Why does this construction cause a Type Mismatch error in Scala?

for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

<console>:6: error: type mismatch;
 found   : List[(Int, Int)]
 required: Option[?]
       for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

If I switch the Some with the List it compiles fine:

for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))

This also works fine:

for (first <- Some(1); second <- Some(2)) yield (first,second)

Solution

  • For comprehensions are converted into calls to the map or flatMap method. For example this one:

    for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)
    

    becomes that:

    List(1).flatMap(x => List(1,2,3).map(y => (x,y)))
    

    Therefore, the first loop value (in this case, List(1)) will receive the flatMap method call. Since flatMap on a List returns another List, the result of the for comprehension will of course be a List. (This was new to me: For comprehensions don't always result in streams, not even necessarily in Seqs.)

    Now, take a look at how flatMap is declared in Option:

    def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]
    

    Keep this in mind. Let's see how the erroneous for comprehension (the one with Some(1)) gets converted to a sequence of map calls:

    Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))
    

    Now, it's easy to see that the parameter of the flatMap call is something that returns a List, but not an Option, as required.

    In order to fix the thing, you can do the following:

    for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)
    

    That compiles just fine. It is worth noting that Option is not a subtype of Seq, as is often assumed.