Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Futures callback hell

I've been reading many times about Scala Futures reducing callback problems. I've got a code that started to look problematic.

val a = Future(Option(Future(Option(10))))

a.map { b =>
  b.map { c =>
    c.map { d =>
      d.map { res =>
        res + 10
      }
    }
  }
} 

How can I make this code more flat?

//Edit @againstmethod

for{
  b <- a
  c <- b
  d <- c
  res <- d
} yield res + 10

This code won't compile

Error:(21, 8) type mismatch; found : Option[Int] required:
scala.concurrent.Future[?] res <- d
^

like image 462
Krzysztof Wende Avatar asked Feb 05 '15 12:02

Krzysztof Wende


1 Answers

You can use a for comprehension. In example:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

object Stuff extends App {
  val result = for {
    f1 <- Future { 10 + 1 }
    f2 <- Future { f1 + 2 }
  } yield f2
  result.onComplete(println)
}

Where result will be 13.

Any class that implements a proper map and flatMap function can be used this way in a for.

If you don't mind another dependency, you can also use a library like scalaz and explicitly use monadic binding to flatten things out (EDIT encoded some Option types to address a comment below):

import scalaz._
import Scalaz._
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.{Success,Failure}

object BindEx extends App {

  def f1(i: String): Future[Int] = Future { i.length }
  def f2(i: Int): Future[Option[Double]] = Future { Some(i / Math.PI) }
  def f3(i: Option[Double]): Future[Option[Double]] = Future { 
    i match {
      case Some(v) => Some(Math.round(v))
      case _ => None
    } 
  }

  val result = 
    Monad[Future].point("Starting Point") >>= 
    f1 >>= 
    f2 >>=
    f3

  result.onComplete { x => 
    x match {
      case Success(value) => println("Success " + value)
      case Failure(ex) => println(ex)
    }  
  }

  Await.result(result, 1 seconds)
}

And finally, if you just have parallel operations that you want to bind after all have succeeded that are independent, you can use scalaz applicative builder:

  val result = (
    Future { 10 + 10 } |@| 
    Future { 20 - 3 } |@| 
    Future { Math.PI * 15 }
  ) { _ + _ / _}
  println(Await.result(result, 1 seconds))

This will let all 3 futures complete, then apply block to the 3 arguments.

like image 117
Rich Henry Avatar answered Sep 28 '22 08:09

Rich Henry