• Java Future modeled async operations • Minimal ways to observe and react • Twitter and Akka • Scala SIP-14, Jan 2012 • Scala standard Future in 2.10 3
a Future? SCALA FUTURES • 3 states • Unresolved • Resolved with Success and a value • Resolved with Failure and a Throwable • Immediately resolved instances • Promises • Rich set of combinators in class and companion object
with apply() SCALA FUTURES 8 import scala.concurrent.ExecutionContext.Implicits.global val myFuture: Future[Int] = Future { calculateSlowly() } Creating a Future with Promise val myPromise = Promise[Int]() val myNewFuture = myPromise.future val res = calculateSlowly() myPromise.success(res)
an ExecutionContext SCALA FUTURES • Transform body is submitted to EC as a Callable • Global execution context – ForkJoin pool • Build from any Executor or ExecutionService • Why might you chose one over another? • Control over pool size and characteristics • ForkJoinPool is not ideal for long blocking operations • Certain work in a particular pool 14
model is different from Scala TWITTER FUTURES • Transform body is executed in the prior thread • Pipeline executed by same thread • Work can be forked to a new pool • Will these necessarily be in order? Depth first or something else? • If already resolved, new callback executes immediately in caller thread • Future.apply() runs body immediately in caller thread • New Future creation needs explicit FuturePool 21
futures TWITTER FUTURES val pool = FuturePool.unboundedPool val notf = Future { // executes immediately in caller thread calculate() } val f = pool { // executes in the pool calculate() } 22
cancellation TWITTER FUTURES • Promise raise() – marks promise as interrupted • Future is NOT resolved • Promise isInterrupted() • Running calculation is not stopped • Calculation can check isInterruped() and interrupt itself • raiseWithin() – NEW future which fails after the timeout 23
val pool = FuturePool.unboundedPool val promise = new Promise[Int]() pool { val r1 = expensiveCalculation() promise.isInterrupted match { case Some(t) => promise.setException(t) case None => promise.setValue(nextCalculation(r1)) } } promise.raise(new IllegalStateException(“I give up”)) 24
LISTENABLEFUTURE • Google Guava core library • Brought functional collections to Java • ListenableFuture provides Futures with callbacks • From ca 2011 • Before Scala Future • Before CompletableFuture
from Scala GUAVA LISTENABLEFUTURE • Not chainable • Requires type hint • Relies on GuavaFunction and AsyncFunction • transform() vs transformAsync() (in Guava 20.0) • ExecutionContext required (in Guava 21.0) • Consider adapters like Gilt Foundation Classes (gfc-guava) 28
COMPLETABLEFUTURE • Java8 • Future with callbacks • Acts as both Promise and Future • Creation • As Promise • In an executor via supplyAsync(Supplier) • In an executor via runAsync(Runnable)
COMPLETABLEFUTURE val cf = new CompletableFuture[Int]() val x1: CompletableFuture[Int] = cf.thenApplyAsync( (i: Int) => scaleTransform(i)) // final stage – deal with the result x1.handle((r, t) => if (r != null) doHappyPath(r) else if (t != null) doExceptionPath(t)) // some time later cf.complete(41) 32
COMPLETABLEFUTURE • Different methods to do direct or async operations • apply(), applyAsync() • handle() handleAsync() • Regular version uses the same thread • Async versions use default executor or specified executor • Default executor is ForkJoinPool.commonPool() • Pool sized based on number of cores • New thread if commonPool is not configured for parallelism 33
and combinators compare? JAVA COMPLETABLEFUTURE • CompletableFuture operations provide variants: • Runnable / Supplier • Function / Consumer • No recoverWith, just recover (exceptionally) • Cannot replace a failure with an unresolved fallback • Combinators execute next step without intermediate Future • Scala zip() resolves with a tuple, map() to execute next step • foo.thenCombine(bar, (a,b) => doSomethingWith(a,b)) • Overall handler method taking nullable success and failure • handle(BiFunction<? super T,Throwable,? extends U> fn) 34
thought of as lazy Futures MONIX TASKS • Scalaz, Monix, FS2 • General delayed effects see Scalaz 8 IO or Cats Effect • Explicitly run: attemptRun(), runAsync(), unsafeRunAsync() • No unwanted computations • No leaked computations • Memoization • Run every time by default • Memoize the result on demand • Trampolined • Cancelable
MONIX TASKS import monix.execution.Scheduler.Implicits.global import monix.execution.CancelableFuture import monix.eval.Task import scala.util.{Success, Failure} val task = Task { calculation() } val cancelable = task.runAsync { result => result match { case Success(value) => ??? case Failure(ex) => ??? }} val res = task.memoize 37
Trampoline MONIX TASKS • Controlled by Scheduler • BatchedExecution – Async boundary forced after a maximum size • AlwaysAsyncExecution • SynchronousExecution • Units of work are executed in a loop on a single thread • Work is forked to a new thread upon failure or async boundaries • Explicit fork to new Scheduler via Task.fork(s) • Task.fork(fa: Task[A]): Task[A] • mytask.asyncBoundary: Task[A] 39
boundaries MONIX TASKS import monix.execution.Scheduler lazy val io = Scheduler.io(name="my-io") val source = Task(println(s"${Thread.currentThread.getName}")) val forked = Task.fork(source, io) val task = { source // executes on global .flatMap(_ => forked) // executes on io .asyncBoundary // switch back to global .doOnFinish(_ => onFinish) // executes on global .runAsync } 40
Java Promise-like creation Promise Promise SettableFuture CompletableFuture Async creation Future.apply() myPool.apply() ListeningDecorator execSvc.submit() supplyAsync() Execution model Run in ExecutionContext Completing thread runs Completing thread or executor (method overload) Completing thread or executor (different method) Supports for- comprehension Yes Yes No (yes with gfc-guava) No (possible with conversions) Cancellation No Yes (cooperative) No Yes (no interrupt despite argument) Java/Scala interoperation Implement e.g. PartialFunction from Java Variants provided for interop Java fair, Scala bad Better with gfc-guava Java good, type inference issues Future comparison CONCLUSIONS 42
recommend? CONCLUSIONS • Use whatever your libraries use • If you aren’t introducing Twitter, stick with Scala unless you need cancellation or monitoring combinators • If you need to mix Scala and Twitter, use Bijection • If you need to interop with Java, prefer CompletableFuture • At this point, avoid Guava ListenableFuture • Watch this space – lots of progress with Monix, Scalaz8 IO, and Cats Effect 43