Search code examples
scalafor-loopfutureimplicit-conversionimplicit

Scala implicit conversion from Try to Future


I'm trying to mix Trys with Futures in a for loop in Scala, without explicitly converting the Trys to Futures with Future.fromTry. It looks like that it works automatically in some cases, but not in others.

The following snippet fails with

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.util._

for {
  a <- Try(5)
  b <- Future(10)
} yield { a + b } 

type mismatch;
found : scala.concurrent.Future[Int]
required: scala.util.Try[?]
b <- Future { 10 }
^
Compilation Failed

On the other hand if I remove the keyword yield, it works:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.util._

for {
  a <- Try(5)
  b <- Future(10)
} { println(a + b) }

It also works when I rewrite the for loop into a nested foreach or map:

@ Try(5) foreach { a => Future(10) foreach { b => println(a + b) } }
15


@ Try(5) map { a => Future(10) map { b => a + b } }
res5: Try[Future[Int]] = Success(Future(Success(15)))

Could someone explain me why is this happening? Is this a bug? Or am I missing something?

ps. it's the same behaviour in Scala 2.11 and 2.12.


Solution

  • foreach returns a Unit type; therefore a "common" type.

    Without yield keyword, the compiler interprets your for-comprehension as:

    Try(5).foreach(a => Future(10).foreach(b => println(a + b)))
    

    You can deal with Future and Try (two distinct monads) as long as you are chaining them around foreach.

    If you add yield keyword, the compiler will interpret your for-comprehension with flatMap/map; as following:

    Try(5).flatMap(a => Future(10).map(b => a + b))
    

    The Try#flatMap expects a function having a Try as return type, but it gets a Future, making the whole not compiling.

    TL;DR: foreach does not expect matching of function types during chaining since it returns Unit in all cases; that's why it compiles.

    Note that the following:

    Try(5) map { a => Future(10) map { b => a + b } }
    

    works since map does not need to flatten types; so wrapping compiles with distinct "effects". Flattening distinct types make the compiler fails; as flatMap attempts to do.