Search code examples
scalacompiler-errorsyieldtype-mismatchfor-comprehension

for-comprehension yield raises type mismatch compiler error


I want to extract from Iterable[Try[Int]] a list of all valid values (Iterable[Int])

val test = List(
    Try(8), 
    Try(throw new RuntimeException("foo")), 
    Try(42), 
    Try(throw new RuntimeException("bar"))
)

The following is the way to print all valid values from test:

for {
    n <- test
    p <- n
} println(p)

// Output
// 8
// 42

However, when I tried to save the valid values to list I have received an error:

val nums: Seq[Int] = for {
    n <- list
    p <- n    // Type mismatch. Required: IterableOnce[Int], found Try[Int]
} yield(p)
println(nums)

How to fix the error and why it was raised?


Solution

  • Try collect

    test.collect { case Success(value) => value }
    // res0: List[Int] = List(8, 42)
    

    In a for-comprehension format that corresponds to

    for { Success(p) <- test } yield p
    

    Both make use of Constructor Patterns which under the hood perform isInstanceOf type test followed by asInstanceOf type cast. Verbosly that corresponds to something like

    test
      .filter (_.isInstanceOf[Success[Int]])
      .map    (_.asInstanceOf[Success[Int]].value)
    

    The following for-comprehension does not work because the monads in it have to align

    for {
      n <- test  // List monad
      p <- n     // does not align with Try monad
    } yield (p)
    

    The above for-comprehension desugars to

    test.flatMap((n: Try[Int]) => n.map((p: Int) => p))
    

    and looking at the signature of flatMap we see it expects a function

    Try[Int] => IterableOnce[Int]
    

    whilst we provide

    Try[Int] => Try[Int]
    

    because n.map((p: Int) => p) returns Try[Int]. Now the following for-comprehension is a whole different beast

    for {
        n <- test
        p <- n
    } println(p)
    

    because of the absence of yield it desugars to

    test.foreach((n: Try[Int]) => n.foreach((p: Int) => println(p)))
    

    where foreach expects a function of type

    Try[Int] => Unit
    

    which we indeed provide because n.foreach((p: Int) => println(p)) indeed returns Unit.