Scala Futures

Lorenzo Barasti

EPISODE II

MAY THE FORs BE WITH YOU

What is a Future?

A Future is
an object holding a value
which may become available
​at some point.

Scala Futures

What is it
good for?

What is it good for?

Running expensive computations...

val inverseFuture: Future[Matrix] = Future {
  fatMatrix.inverse()
}

...off the main thread

Retrieving data from a database...

val query = coffees
  .filter(_.name.startsWith("ESPRESSO"))
  .map(_.name)
val coffeeNames: Future[Seq[String]] = db.run(query.result)

...without blocking

What is it good for?

Issuing an HTTP request...

val responseFuture: Future[HttpResponse] =
  Http().singleRequest(HttpRequest(uri = "http://akka.io"))

...without having to wait for the response

What is it good for?

Running concurrent computations

val vectors: Seq[(Float, Float)]
def length(vector: (Float, Float)): Float

val lengths: Seq[Float] = vectors.map(v => length(v))

What is it good for?

Running concurrent computations

val vectors: Seq[(Float, Float)]
def length(vector: (Float, Float)): Float

val lengths: Seq[Future[Float]] = vectors.map(v => Future(length(v)))

What is it good for?

But why?

But why?

Responsiveness

Error Isolation

Performance

Code Composability

How does it work?

How does it work?

Future {
  Matrix.factorize(mat)
} // => Future[(Matrix, Matrix)]

Task A

db.run(query)
// => Future[Seq[Coffee]]

Task B

http.singleRequest(request)
// => Future[HttpResponse]

Task C

How does it work?

Task A

db.run(query)
// => Future[Seq[Coffee]]

Task B

http.singleRequest(request)
// => Future[HttpResponse]

Task C

Execution

Context

passed implicitly

Future {
  Matrix.factorize(mat)
} // => Future[(Matrix, Matrix)]

How does it work?

Shared Queue

Task A

Execution Context

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Execution Context

Scheduler

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task B

Task C

Task D

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task D

Task B

Task C

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task D

Task B

Task C

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task D

Task B

Task C

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task D

Task B

Task C

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task D

Task B

Task C

Done

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task D

Task B

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task A

Task D

Task B

Task E

How does it work?

Shared Queue

Fixed Thread Pool

Thread 1

Thread 2

Thread 3

Task D

Task B

Task E

Task A

Done

OK, but how do I
use them?

How to

Handling a successful future with .forEach

def hasCycle(g: Graph): Boolean

Future(hasCycle(g)).forEach { cycleFound =>
  if(cycleFound) println("I found a cycle")
  else println("The graph is acyclic")
}
// => _: Future[Unit]

Handling a successful future with .map

def hasCycle(g: Graph): Boolean
def topologicalOrdering(g: Graph): Seq[Int]

Future(hasCycle(g)).map { cycleFound =>
  if(!cycleFound) Some(topologicalOrdering(g))
  else None
}
// => _: Future[Option[Seq[Int]]]

How to

Dealing with side-effects with .andThen

def colouring(g: Graph, upperBound: Int): Int
def findColouringUpperBound(g: Graph): Future[Int]

findColouringUpperBound(g).andThen {
  case Failure(ex) => println(
    "Could not compute upper bound")
  case Success(upperBound) =>
    updateUpperBoundCache(g, upperBound)
}.map(upperBound => colouring(g, upperBound))
// => Future[Int]

How to

Running dependent operations with .flatMap

def colouring(g: Graph, upperBound: Int): Future[Int]
def findColouringUpperBound(g: Graph): Future[Int]

findColouringUpperBound(g)
  .recover(_ => g.size)
  .map(upperBound => colouring(g, upperBound))
  .andThen {
    case Success(Success(colours)) => println(g, colours)
  }
// => Future[Future[Int]]

How to

Running dependent operations with .flatMap

def colouring(g: Graph, upperBound: Int): Future[Int]
def findColouringUpperBound(g: Graph): Future[Int]

findColouringUpperBound(g)
  .recover(_ => g.size)
  .flatMap(upperBound => colouring(g, upperBound))
  .andThen {
    case Success(colour) => println(g, colour)
  }
// => Future[Int]

How to

Handling failures with .recover

def colouring(g: Graph, upperBound: Int): Int
def findColouringUpperBound(g: Graph): Future[Int]

findColouringUpperBound(g).recover { exeption =>
  println("Could not compute upper bound, " +
    "using graph size as fallback")
  g.size
}.map(upperBound => colouring(g, upperBound))
// => Future[Int]

How to

What about my
old code?

Effective Refactoring

Avoid instantiating futures on known values

val userCache: Map[Int, User]
def getUserFromDb(id: Int): Future[User]

val userId: Int = 42

def getUser(id: Int): Future[User] = {
  if(userCache.has(id))
    Future(userCache(id))
  else
    getUserFromDb(id)
}

Avoid instantiating futures on known values

val userCache: Map[Int, User]
def getUserFromDb(id: Int): Future[User]

val userId: Int = 42

def getUser(id: Int): Future[User] = {
  if(userCache.has(id))
    Future.successful(userCache(id))
  else
    getUserFromDb(id)
}

Effective Refactoring

Use .andThen to wrap side-effects and return the original value

def transform(user: User): PowerUser

storage.selectFileById(id).map { result =>
  log.debug(s"Retrieved object:$id filesSize:'${result.size}'")
  result
}.map(transform)
// => Future[PowerUser]

Effective Refactoring

Use .andThen to wrap side-effects and return the original value

def transform(user: User): PowerUser

storage.selectFileById(id).andThen {
  case Success(result) =>
    log.debug(s"Retrieved object:$id filesSize:'${result.size}'")
}.map(transform)
// => Future[PowerUser]

Effective Refactoring

Use .transform/.transformWith for fine grained control over failures

def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]

val g: Graph

getColourUpperBound(g).flatMap(n => colouring(g, n))
  .recover(_ => colouringHeuristic(g))
// => Future[Seq[Int]]

Effective Refactoring

Which error are we handling?

Use .transform/.transformWith for fine grained control over failures

def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]

val g = Graph(...)

getColourUpperBound(g).transform {
  case Success(n) => colouring(g, n)
  case Failure(_) => colouringHeuristic(g)
}
// Future[Seq[Int]]

Effective Refactoring

Use for comprehension

def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
def sendResult(colouring: Seq[Int]): Future[HttpResponse]
def report(result: Seq[Int]): Report

val g = Graph(...)

getColourUpperBound(g).flatMap { upperBound =>
  colouring(g, upperBound).flatMap { result =>
    sendResult(result).map { _ =>
      report(result)
    }
  }
}
// Future[Report]

Effective Refactoring

Use for comprehension

def getColourUpperBound(g: Graph): Future[Int]
def colouring(g: Graph, n: Int): Future[Seq[Int]]
def colouringHeuristic(g: Graph): Future[Seq[Int]]
def sendResult(colouring: Seq[Int]): Future[HttpResponse]
def report(result: Seq[Int]): Report

val g = Graph(...)

for {
  upperBound <- getColourUpperBound(g)
  result <- colouring(g, upperBound)
  _ <- sendResult(result)
} yield report(result)
// Future[Report]

Effective Refactoring

NICE!

So, I have this Actor handling a Future...

Actors and Futures

Actor A

UserProfile
Actor

UserProfile
API

Akka Application

Actors and Futures

Actor A

UserProfile
Actor

UserProfile
API

Akka Application

GetUserProfile

Actors and Futures

Actor A

UserProfile
Actor

UserProfile
API

Akka Application

GetUserProfile

Actors and Futures

Actor A

UserProfile
Actor

UserProfile
API

Akka Application

GetUserProfile

Actors and Futures

Actor A

UserProfile
Actor

UserProfile
API

Akka Application

GetUserProfile

UserProfile

Actors and Futures

Actor A

UserProfile
Actor

UserProfile
API

Akka Application

GetUserProfile

Cache

Actors and Futures

Actor A

UserProfile
Actor

UserProfile
API

Akka Application

GetUserProfile

UserProfile

Cache

Actor A: just ask

val userProfileActor: ActorRef
val userId: UserId

(userProfileActor ? GetUserProfile(userId))
  .mapTo[UserProfile]
  .andThen { case profile => println(userId, profile) }

Actors and Futures

Send a message and wait for a response

Actor A: just ask

val userProfileActor: ActorRef
val userId: UserId

(userProfileActor ? GetUserProfile(userId))
  .mapTo[UserProfile]
  .andThen { case profile => println(userId, profile) }

Actors and Futures

The type of the response is Any

val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty

def receive: Receive = {
  case GetUserProfile(userId) =>
    cache.get(userId) match {
      case Some(profile) => sender() ! profile
      case None =>
        fetch(userId).map { profile =>
          cache += userId -> profile
          sender() ! profile
        }
    }
}

Actors and Futures

UserProfileActor

ALERT! Closing over sender()

val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty

def receive: Receive = {
  case GetUserProfile(userId) =>
    cache.get(userId) match {
      case Some(profile) => sender() ! profile
      case None =>
        fetch(userId).map { profile =>
          cache += userId -> profile
          sender() ! profile
        }
    }
}

Actors and Futures

UserProfileActor

Won't return the sender you expect

val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty

def receive: Receive = {
  case GetUserProfile(userId) =>
    cache.get(userId) match {
      case Some(profile) => sender() ! profile
      case None =>
        val ref = sender()
        fetch(userId).map { profile =>
          cache += userId -> profile
          ref ! profile
        }
    }
}

Actors and Futures

UserProfileActor

Capture the sender in the right context

val http = Http(context.system)
var cache: Map[UserId, UserProfile] = Map.empty

def receive: Receive = {
  case GetUserProfile(userId) =>
    cache.get(userId) match {
      case Some(profile) => sender() ! profile
      case None =>
        val ref = sender()
        fetch(userId).map { profile =>
          cache += userId -> profile
          ref ! profile
        }
    }
}

Actors and Futures

UserProfileActor

Suspicious...

val http: HttpRef
var cache: Map[UserId, UserProfile]
var subscribers: Map[UserId, Seq[ActorRef]]

def receive: Receive = {
  case GetUserProfile(userId) =>
    cache.get(userId) match {
      case Some(profile) => sender() ! profile
      case None =>
        if(subscribers(userId).isEmpty)
          fetch(userId).foreach { profile =>
            cache += userId -> profile
            subscribers(userId).foreach(_ ! profile)
            subscribers -= userId
          }
        subscribers += userId -> (sender() +: subscribers(userId))
    }
}

Actors and Futures

UserProfileActor

Changing the actor state

val http: HttpRef
var cache: Map[UserId, UserProfile]
var subscribers: Map[UserId, Seq[ActorRef]]

def receive: Receive = {
  case GetUserProfile(userId) =>
    cache.get(userId) match {
      case Some(profile) => sender() ! profile
      case None =>
        if(subscribers(userId).isEmpty)
          fetch(userId).map(FetchedUserProfile) pipeTo self
        val updatedSubs = sender() +: subscribers(userId)
        subscribers += userId -> updatedSubs
    }

  case FetchedUserProfile(profile) =>
    cache += profile.userId -> profile
    subscribers(profile.userId).foreach(_ ! profile)
    subscribers = subscribers - profile.userId

Actors and Futures

Wooooooo!

Actors and Futures

What if the call fails?

def receive: Receive = {
  // ...
  case failure @ Status.Failure(InvalidResponse(userId, statusCode)) =>
    subscribers(userId).foreach(_ ! failure)
    subscribers = subscribers - userId

  case failure @ Status.Failure(MalformedJsonException(_)) =>
    subscribers(userId).foreach(_ ! failure)
    subscribers = subscribers - userId
}

Can you say
that again?

Your application
with Futures

Fast

Responsive

Awesome

Your Futures

Composable API

Maintainable code

Available in many colours!

Should have used Scala Futures

Bad error handling

pipeTo

Actors

Akka Application

ask

Concept

Lorenzo Barasti

Jacob Taylor-Hindle

Powered by

@lbarasti

lbarasti.github.io

@glassandonehalf

References

Scala Futures

By Lorenzo Barasti

Scala Futures

EPISODE II - MAY THE FORs BE WITH YOU

  • 1,248