Scala Futures

A Brief Primer

What Are Futures?

A data structure for managing the results of a concurrent operation.

Futures in Scala

  • Futures are builtin to Scala, in scala.concurrent

  • Since 2.10, Scala Futures have been based on Akka's implementation

  • We're going to talk briefly about how to manage them

  • The Akka docs on Scala Futures are a great guide, too
    • This presentation is based on them...

Execution Contexts

  • Futures need an "Execution Context" to execute on 
     
  • For now, let's assume we are using the default provided by Scala...
import scala.concurrent._
import ExecutionContext.Implicits.global

Creating a Future

 
import scala.concurrent.Future

val future = Future {
  "Hello" + "World"
}

future foreach println

These operations are asynchronous ... the println only runs when the Future completes

Creating a Precompleted Future

// Already "done" successfully Future
val future = Future.successful("Yay!")

// Already "failed" Future
val otherFuture = Future.failed[String](
  new IllegalArgumentException("Bang!")
)

Promises

val promise = Promise[String]()

val theFuture = promise.future

promise.success("hello")

Promises are "for later" and can provide a completed Future

Futures as a Monad

  • Futures are Monadic, and can be handled in a stream of operations
     
  • map, flatMap, and withFilter mean we can do everything we need in a for comprehension, too

map on Futures

val f1 = Future {
  "Hello" + "World"
}

val f2 = f1 map { x =>
  x.length
}

f2 foreach println

map does not mutate the Future, but provides a new one... and doesn't block!

Nesting Futures

val f1 = Future {
  "Hello" + "World"
}

val f2 = Future.successful(3)

val f3 = f1 map { x =>
  f2 map { y =>
    x.length * y
  }
}

f3 foreach println

Double mapping gives us a Future[Future[Int]]...

Not good.

Nesting Futures

val f1 = Future {
  "Hello" + "World"
}

val f2 = Future.successful(3)

val f3 = f1 flatMap { x =>
  f2 map { y =>
    x.length * y
  }
}

f3 foreach println

Using flatMap will flatten us out...

But it's probably better to use for comprehensions here.

"Conditional Propagation"

val future1 = Future.successful(4)

val future2 = future1.filter(_ % 2 == 0)
 
future2 foreach println
 
val failedFilter = future1.filter(_ % 2 == 1).recover {
  // When filter fails, it will have a 
  // java.util.NoSuchElementException
  case m: NoSuchElementException => 0
}
 
failedFilter foreach println

If we only want to continue with a Future under certain conditions,  filter helps us out

for comprehensions with Future

val f = for {
  a <- Future(10 / 2) // 10 / 2 = 5
  b <- Future(a + 1) //  5 + 1 = 6
  c <- Future(a - 1) //  5 - 1 = 4
  if c > 3 // Future.filter
} yield b * c //  6 * 4 = 24
 
// Note that the execution of futures a, b, and c
// are not done in parallel.
 
f foreach println

Because for comprehensions use map, flatMap, and filter, execution is sequential here.

Composing Futures

  • Scala provides a number of tools for composing Future
     
  • We're going to use Akka Actors here for a few examples
     
  • All you really need to know is this: ask returns a Future!

Combining lots of Futures

// oddActor returns odd numbers sequentially from 1 as a List[Future[Int]]
val listOfFutures = List.fill(100)(
  akka.pattern.ask(oddActor, GetNext).mapTo[Int]
)
 
// now we have a Future[List[Int]]
val futureList = Future.sequence(listOfFutures)
 
// Find the sum of the odd numbers
val oddSum = futureList.map(_.sum)

oddSum foreach println

sequence transforms a List[Future[T]] into a Future[List[T]]

Consider traverse instead

val futureList = Future.sequence((1 to 100).toList.map { x => 
  Future(x * 2 - 1))
}

val oddSum = futureList.map(_.sum)

oddSum foreach println

The only problem here is sequence creates an intermediate List[Future[Int]]

val futureList = Future.traverse((1 to 100).toList) { x => 
  Future(x * 2 - 1)
}

val oddSum = futureList.map(_.sum)

oddSum foreach println

traverse will create a Traversable[Future[T]] without an intermediate List step  here

Using fold with Futures

// Create a sequence of Futures
val futures = for (i <- 1 to 1000) yield Future(i * 2)

val futureSum = Future.fold(futures)(0)(_ + _)

futureSum foreach println

fold here works just like foldLeft

reduce as an Alternative

// Create a sequence of Futures
val futures = for (i <- 1 to 1000) yield Future(i * 2)

val futureSum = Future.reduce(futures)(_ + _)

futureSum foreach println

reduce is useful when we don't have a start value, and instead want to "start" with the result of the first Future

callbacks on Future

  • Futures provide a number of callbacks for result management
     
  • Each takes a PartialFunction[A, _]
     
  • These are meant for when you want to Side Effect on a result (they return Unit)
     
  • The three primary ones:
  • onSuccess - when this Future succeeds... Passes any result type
  • onFailure - when this Future fails... Passes Throwable instances
  • onComplete - when this Future completes ... (passes a scala.util.Try)

onSuccess

future onSuccess {
  case "bar"     => println("Got my bar alright!")
  case x: String => println("Got some random string: " + x)
}

onSuccess only runs when no Exception is thrown...

onFailure

future onFailure {
  case ise: IllegalStateException if ise.getMessage == "OHNOES" =>
  //OHNOES! We are in deep trouble, do something!

  case e: Exception =>
  //Do something else
}

onFailure can "catch" any Exception ...

Note that Future does not "throw" outside itself!

onComplete

future onComplete {
  case Success(result)  => doSomethingOnSuccess(result)

  case Failure(failure) => doSomethingOnFailure(failure)
}

onComplete always runs when a Future completes, passing a scala.util.Try (instances of Success[T] or Failure[Throwable])

falling back

val future4 = future1 fallbackTo future2 fallbackTo future3

future4 foreach println

fallbackTo combines two Future instances, holding the successful value of the second Future if the first fails

zipping

val future3 = future1 zip future2 map { case (a, b) => 
  a + " " + b 
}

future3 foreach println

We can also zip two Future instances together creating a Tuple of successful results

Handling Exceptions with a value

  • Remember that onFailure and onComplete return Unit and are only useful for Side Effects like logging

  • If you need to handle an Exception returning a default result, use recover and recoverWith

recovering

val future = akka.pattern.ask(actor, msg1) recover {
  case e: ArithmeticException => 0
}

future foreach println

recover lets us return a "default" value in the case of an Exception

recovering with another Future

val future = akka.pattern.ask(actor, msg1) recoverWith {

  case e: ArithmeticException => Future.successful(0)

  case foo: IllegalArgumentException =>
    Future.failed[Int](new IllegalStateException("All br0ken!"))

}

future foreach println

recoverWith is to flatMap as recover is to map...

Useful when you want to "recover" with another Future

Questions?

Scala Futures

By Brendan McAdams

Scala Futures

  • 1,568
Loading comments...

More from Brendan McAdams