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
- 2,576