Skip to content

Instantly share code, notes, and snippets.

@mariusae
Last active December 11, 2015 21:18
Show Gist options
  • Select an option

  • Save mariusae/4660893 to your computer and use it in GitHub Desktop.

Select an option

Save mariusae/4660893 to your computer and use it in GitHub Desktop.
Concurrent Programming with Futures
===================================
A *Future* is a placeholder for the result of an asynchronous
operation. Examples of such operations are:
- an RPC to a remote host
- a long computation in another thread
- reading from disk
Note that these operations are fallible: remote hosts could crash,
computations might throw an exception, disks could fail, etc. etc.
A `Future[T]`, then, occupies exactly one of three states:
- Empty (pending)
- Succeeded (with a result of type `T`)
- Failed (with a `Throwable`)
It is possible to directly query a Future for its state via its `poll` method,
but this is typically not useful. Instead, a callback may be registered to
receive the results once they are made available:
::
val f: Future[Int]
f onSuccess { res =>
println("The result is " + res)
}
which will be invoked only on success. Callbacks may also be registered
to account for failures:
::
f onFailure { cause: Throwable =>
println("f failed with " + cause)
}
This is more useful, but still cumbersome. The power of Futures lie in
how they *compose*. Most operations can be broken up into smaller
operations which in turn constitute the *composite operation*. Futures
makes it easy to create such composite operations.
Consider the simple example of updating a value in a database. This
typically involves
1. Checking the current value
2. Computing the next value
3. Setting the new value
This is an example of *sequential* composition: in order to do the
next step, we must have succesfully completed the previous one. With
Futures, this is called `flatMap`. The result of `flatMap` is a Future
representing the result of this composite operation.
::
def getValue(key: String): Future[Value]
def setValue(key: String, value: Value): Future[Unit]
def computeNextValue(value: Value): Value
val key = "somekey"
val f: Future[Unit] = getValue(key) flatMap { value =>
val nextValue = computeNextValue(value)
setValue(key, nextValue)
}
f onSuccess { _: Unit =>
println("Successfully completed the update")
}
`f` represents the *composite* operation. It is the result of first
retrieving the current value, computing the next value and setting
that value. If either of the smaller operations fail
(`getCurrentValue`, `setCurrentValue`), the composite operation also
fails.
The astute reader may have noticed something peculiar: this is
typically the job of the semicolon! That is not far from the truth:
semicolons sequence two statements, and with traditional I/O
operations, have the same effect as `flatMap` does above (the
exception mechanism takes the role of a failed future). Futures
are much more versatile, however, as we'll see.
It is also possible to compose futures *concurrently*. We can extend
our above example to demonstrate: let's say that in order to compute
the next value, we needed a number of values from different sources. A
common real-world example of this is to create a templatized web page:
you often need bits and pieces of data from various sources
(databases, caches, etc.) in order to output the final webpage.
Concurrent composition is provided by `Future.join`:
::
def computeCompositeValue(vs: Value*): Value = ...
val fs: Seq[Future[Value]] = Seq(
getValue("key1"),
getValue("key2"),
getValue("key3"))
val joined: Future[(Value, Value, Value)] = Future.join(fs:_*)
val f: Future[Unit] = joined flatMap { case (v1, v2, v3) =>
val compositeValue = computeCompositeValue(v1, v2, v3)
setValue("compositeKey", compositeValue)
}
Here we have combined both concurrent and sequential composition:
first we gather the values for the three keys, then we set the
composite value. That is, future `f` is the result of the three `get`
operations executing concurrently then, when they are all done,
computing the composite value and setting it in our data store.
As with sequential composition, concurrent composition also propagates
failures: the future `joined` will fail if any of the underlying
futures do.
Futures are also flexible enough to write your own combinators: this
is quite useful, and gives rise to a great amount of modularity in
distributed systems as common patterns can be cleanly abstracted.
Other resources
---------------
`Effective Scala`_ contains a `section discussing futures`_
As of Scala 2.10, the Scala standard library has its own futures
implementation and API, described here_. Note that
this is largely similar to the API used in Finagle
(*com.twitter.util.Future*), but there are still some naming
differences.
Akka_ also has a `section dedicated to futures`_.
.. _Akka: http://akka.io/
.. _`Effective Scala`: <http://twitter.github.com/effectivescala/>`_
.. _`section discussing futures`: http://twitter.github.com/effectivescala/#Twitter's%20standard%20libraries-Futures
.. _here: http://docs.scala-lang.org/overviews/core/futures.html
.. _`section dedicated to futures`: http://doc.akka.io/docs/akka/2.1.0/scala/futures.html
@mariusae
Copy link
Author

4: i've reworded this
18: this is a well-defined concept in Scala and Java
43: i'd like to avoid comparison for now--just explain the facts
50: fixed
64: fixed by adding signatures

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment