From c1b1969a3916f201d85b6abc885d26751b55ac27 Mon Sep 17 00:00:00 2001 From: Aleksandar Prokopec Date: Thu, 20 Sep 2012 19:24:09 +0200 Subject: [PATCH 1/2] Add futures and promises overview. --- overviews/core/_posts/2012-09-20-futures.md | 8 + overviews/futures/overview.md | 919 ++++++++++++++++++++ 2 files changed, 927 insertions(+) create mode 100644 overviews/core/_posts/2012-09-20-futures.md create mode 100644 overviews/futures/overview.md diff --git a/overviews/core/_posts/2012-09-20-futures.md b/overviews/core/_posts/2012-09-20-futures.md new file mode 100644 index 0000000000..5d6838d0f1 --- /dev/null +++ b/overviews/core/_posts/2012-09-20-futures.md @@ -0,0 +1,8 @@ +--- +layout: overview +title: Futures and Promises API +disqus: true +label-color: success +label-text: Available +partof: futures +--- diff --git a/overviews/futures/overview.md b/overviews/futures/overview.md new file mode 100644 index 0000000000..a09d1835ed --- /dev/null +++ b/overviews/futures/overview.md @@ -0,0 +1,919 @@ +--- +layout: overview-large +disqus: true +title: Overview + +partof: futures +num: 1 +--- + +**By: Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic** + +## Introduction + +Futures provide a nice way to reason about performing many operations +in parallel-- in an efficient and non-blocking way. The idea +is simple, a `Future` is a sort of a placeholder object that you can +create for a result that does not yet exist. Generally, the result of +the `Future` is computed concurrently and can be later collected. +Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code. + +By default, futures and promises are non-blocking, making use of +callbacks instead of typical blocking operations. +To simplify the use of callbacks both syntactically and conceptually, +Scala provides combinators such as `flatMap`, `foreach`, and `filter` used to compose +futures in a non-blocking way. +Blocking is still possible - for cases where it is absolutely +necessary, futures can be blocked on (although this is discouraged). + + + +## Futures + +A `Future` is an object holding a value which may become available at some point. +This value is usually the result of some other computation. +Since this computation may fail with an exception, the `Future` +either holds a result of the computation or an exception in case +the computation failed. +Whenever a `Future` gets either a value or an exception, we say +that the `Future` is **completed**. +When a `Future` is completed with a value, we say that the future +was **successfully completed** with that value. +When a `Future` is completed with an exception, we say that the +`Future` was **failed** with that exception. + +A `Future` has an important property that it may only be assigned +once. +Once a `Future` object is given a value or an exception, it becomes +in effect immutable-- it can never be overwritten with a new value. + +The simplest way to create a future object is to invoke the `future` +method which starts an asynchronous computation and returns a +future holding the result of that computation. +The result becomes available once the future completes. + +Note that `Future` is a type which denotes future objects, whereas +`future` is a method which creates and schedules an asynchronous +computation, and then returns a future object which will be completed +with the result of that computation. + +This is best shown through an example. +Let's assume that we want to use the hypothetical API of some +popular social network to obtain a list of friends for a given user. +We will open a new session used to send requests and then send +the request to obtain a list of friends of a particular user: + + import scala.concurrent._ + import ExecutionContext.Implicits.global + + val session = socialNetwork.createSessionFor("user", credentials) + val f: Future[List[Friend]] = future { + session.getFriends() + } + +Above, we first import the contents of the `scala.concurrent` package +to make the type `Future` and the construct `future` visible. +We will explain the second import shortly. + +We then initialize a session variable which we will use to send +requests to the server, using a hypothetical `createSessionFor` +method. +To obtain the list of friends of a user, a request +has to be sent over a network, which can take a long time. +This is illustrated with the call to the method `getFriends`. +To better utilize the CPU until the response arrives, we should not +block the rest of the program-- this computation should be scheduled +asynchronously. The `future` method does exactly that-- it performs +the specified computation block concurrently, in this case sending +a request to the server and waiting for a response. + +The list of friends becomes available in the future `f` once the server +responds. + +An unsuccessful attempt may result in an exception. In +the following example, the `session` value is incorrectly +initialized, so the computation in the `future` block will yield a `NullPointerException`. +This future `f` is then failed with this exception instead of being completed successfully: + + val session = null + val f: Future[List[Friend]] = future { + session.getFriends + } + +The line `import ExecutionContext.Implicits.global` above imports +the default global execution context. +Execution contexts execute tasks submitted to them asynchronously. +You can think of execution contexts as thread pools. +They are essential for the `future` method because +they handle how and when the asynchronous computation is executed. +You can define your own execution contexts and use them with `future` +as will be shown later, but for now it is sufficient to know that +you can import the default execution context as shown above. + +Our example was based on a hypothetical social network API where +the computation consists of sending a network request and waiting +for a response. +It is fair to offer an example involving an asynchronous computation +which you can try out of the box. Assume you have a text file and +you want to find the position of the first occurence of a particular keyword. +This computation may involve blocking while the file contents +are being retrieved from the disk, so it makes sense to perform it +concurrently with the rest of the computation. + + val firstOccurence: Future[Int] = future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + + +### Callbacks + +We now know how to start an asynchronous computation to create a new +future value, but we have not shown how to use the result once it +becomes available, so that we can do something useful with it. +We are often interested in the result of the computation, not just its +side-effects. + +In many future implementations, once the client of the future becomes interested +in its result, he has to block its own computation and wait until the future is completed-- +only then can he use the value of the future to continue its own computation. +Although this is allowed by the Scala `Future` API as we will show later, +from a performance point of view a better way to do it is in a completely +non-blocking way, by registering a callback on the future. +This callback is called asynchronously once the future is completed. If the +future has already been completed when registering the callback, then +the callback may either be executed asynchronously, or sequentially on +the same thread. + +The most general form of registering a callback is by using the `onComplete` +method, which takes a callback function of type `Try[T] => U`. +The callback is applied to the value +of type `Success[T]` if the future completes successfully, or to a value +of type `Failure[T]` otherwise. + +The `Try[T]` is similar to `Option[T]` or `Either[T, S]`, in that it is a monad +potentially holding a value of some type. +However, it has been specifically designed to either hold a value or +some throwable object. +Where an `Option[T]` could either be a value (i.e. `Some[T]`) or no value +at all (i.e. `None`), `Try[T]` is a `Success[T]` when it holds a value +and otherwise `Failure[T]`, which holds an exception. `Failure[T]` holds +more information that just a plain `None` by saying why the value is not +there. +In the same time, you can think of `Try[T]` as a special version +of `Either[Throwable, T]`, specialized for the case when the left +value is a `Throwable`. + +Coming back to our social network example, let's assume we want to +fetch a list of our own recent posts and render them to the screen. +We do so by calling a hypothetical method `getRecentPosts` which returns +a `List[String]`-- a list of recent textual posts: + + val f: Future[List[String]] = future { + session.getRecentPosts + } + + f onComplete { + case Success(posts) => for (post <- posts) println(post) + case Failure(t) => println("An error has occured: " + t.getMessage) + } + +The `onComplete` method is general in the sense that it allows the +client to handle the result of both failed and successful future +computations. To handle only successful results, the `onSuccess` +callback is used (which takes a partial function): + + val f: Future[List[String]] = future { + session.getRecentPosts + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +To handle failed results, the `onFailure` callback is used: + + val f: Future[List[String]] = future { + session.getRecentPosts + } + + f onFailure { + case t => println("An error has occured: " + t.getMessage) + } + + f onSuccess { + case posts => for (post <- posts) println(post) + } + +The `onFailure` callback is only executed if the future fails, that +is, if it contains an exception. + +Since partial functions have the `isDefinedAt` method, the +`onFailure` method only triggers the callback if it is defined for a +particular `Throwable`. In the following example the registered `onFailure` +callback is never triggered: + + val f = future { + 2 / 0 + } + + f onFailure { + case npe: NullPointerException => + println("I'd be amazed if this printed out.") + } + +Coming back to the previous example with searching for the first +occurence of a keyword, you might want to print the position +of the keyword to the screen: + + val firstOccurence: Future[Int] = future { + val source = scala.io.Source.fromFile("myText.txt") + source.toSeq.indexOfSlice("myKeyword") + } + + firstOccurence onSuccess { + case idx => println("The keyword first appears at position: " + idx) + } + + firstOccurence onFailure { + case t => println("Could not process file: " + t.getMessage) + } + +The `onComplete`, `onSuccess`, and +`onFailure` methods have result type `Unit`, which means invocations +of these methods cannot be chained. Note that this design is intentional, +to prevent suggesting that chained +invocations may imply an ordering on the execution of the registered +callbacks (callbacks registered on the same future are unordered). + +That said, we should now comment on **when** exactly the callback +gets called. Since it requires the value in the future to be available, +it can only be called after the future is completed. +However, there is no guarantee it will be called by the thread +that completed the future or the thread which created the callback. +Instead, the callback is executed by some thread, at some time +after the future object is completed. +We say that the callback is executed **eventually**. + +Furthermore, the order in which the callbacks are executed is +not predefined, even between different runs of the same application. +In fact, the callbacks may not be called sequentially one after the other, +but may concurrently execute at the same time. +This means that in the following example the variable `totalA` may not be set +to the correct number of lower case and upper case `a` characters from the computed +text. + + @volatile var totalA = 0 + + val text = future { + "na" * 16 + "BATMAN!!!" + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'a') + } + + text onSuccess { + case txt => totalA += txt.count(_ == 'A') + } + +Above, the two callbacks may execute one after the other, in +which case the variable `totalA` holds the expected value `18`. +However, they could also execute concurrently, so `totalA` could +end up being either `16` or `2`, since `+=` is not an atomic +operation (i.e. it consists of a read and a write step which may +interleave arbitrarily with other reads and writes). + +For the sake of completeness the semantics of callbacks are listed here: + +1. Registering an `onComplete` callback on the future +ensures that the corresponding closure is invoked after +the future is completed, eventually. + +2. Registering an `onSuccess` or `onFailure` callback has the same +semantics as `onComplete`, with the difference that the closure is only called +if the future is completed successfully or fails, respectively. + +3. Registering a callback on the future which is already completed +will result in the callback being executed eventually (as implied by +1). Furthermore, the callback may even be executed synchronously on +the same thread that registered the callback if this does not cancel +progress of that thread. + +4. In the event that multiple callbacks are registered on the future, +the order in which they are executed is not defined. In fact, the +callbacks may be executed concurrently with one another. +However, a particular `ExecutionContext` implementation may result +in a well-defined order. + +5. In the event that some of the callbacks throw an exception, the +other callbacks are executed regardlessly. + +6. In the event that some of the callbacks never complete (e.g. the +callback contains an infinite loop), the other callbacks may not be +executed at all. In these cases, a potentially blocking callback must +use the `blocking` construct (see below). + +7. Once executed, the callbacks are removed from the future object, +thus being eligible for GC. + + +### Functional Composition and For-Comprehensions + +The callback mechanism we have shown is sufficient to chain future +results with subsequent computations. +However, it is sometimes inconvenient as it may become bulky. +We show this through an example. Assume we have an API for +interfacing with a currency trading service. Suppose we want to buy US +dollars, but only when it's profitable. We first show how this could +be done using callbacks: + + val rateQuote = future { + connection.getCurrentValue(USD) + } + + rateQuote onSuccess { case quote => + val purchase = future { + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + purchase onSuccess { + case _ => println("Purchased " + amount + " USD") + } + } + +We start by creating a future `rateQuote` which gets the current exchange +rate. +After this value is obtained from the server and the future successfully +completed, the computation proceeds in the `onSuccess` callback and we are +ready to decide whether to buy or not. +We therefore create another future `purchase` which makes a decision to buy only if it's profitable +to do so, and then sends a request. +Finally, once the purchase is completed, we print a notification message +to the standard output. + +This works, but is inconvenient for two reasons. First, we have to use +`onSuccess`, and we have to nest the second `purchase` future within +it. Imagine that after the `purchase` is completed we want to sell +some other currency. We would have to repeat this pattern within the +`onSuccess` callback, making the code overly indented, bulky and hard +to reason about. + +Second, the `purchase` future is not in the scope with the rest of +the code-- it can only be acted upon from within the `onSuccess` +callback. This means that other parts of the application do not +see the `purchase` future and cannot register another `onSuccess` +callback to it, for example, to sell some other currency. + +For these two reasons, futures provide combinators which allow a +more straightforward composition. One of the basic combinators +is `map`, which, given a future and a mapping function for the value of +the future, produces a new future that is completed with the +mapped value once the original future is successfully completed. +You can reason about `map`ping futures as you reason about `map`ping +collections. + +Let's rewrite the previous example using the `map` combinator: + + val rateQuote = future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + purchase onSuccess { + case _ => println("Purchased " + amount + " USD") + } + +By using `map` on `rateQuote` we have eliminated one `onSuccess` callback and, +more importantly, the nesting. +If we now decide to sell some other currency, it suffices to use +`map` on `purchase`. + +But what happens if `isProfitable` returns `false`, hence causing +an exception to be thrown? +In that case `purchase` is failed with that exception. +Furthermore, imagine that the connection was broken and that +`getCurrentValue` threw an exception, failing `rateQuote`. +In that case we'd have no value to map, so the `purchase` would +automatically be failed with the same exception as `rateQuote`. + +More formally, if the original future is +completed successfully then the returned future is completed with a +mapped value from the original future. If the mapping function throws +an exception the future is completed with that exception. If the +original future fails with an exception then the returned future also +contains the same exception. This exception propagating semantics is +present in the rest of the combinators, as well. + +One of the design goals for futures was to enable their use in for-comprehensions. +For this reason, futures also have the `flatMap`, `filter` and +`foreach` combinators. The `flatMap` method takes a function that maps the value +to a new future `g`, and then returns a future which is completed once +`g` is completed. + +Lets assume that we want to exchange US dollars for Swiss francs +(CHF). We have to fetch quotes for both currencies, and then decide on +buying based on both quotes. +Here is an example of `flatMap` and `withFilter` usage within for-comprehensions: + + val usdQuote = future { connection.getCurrentValue(USD) } + val chfQuote = future { connection.getCurrentValue(CHF) } + + val purchase = for { + usd <- usdQuote + chf <- chfQuote + if isProfitable(usd, chf) + } yield connection.buy(amount, chf) + + purchase onSuccess { + case _ => println("Purchased " + amount + " CHF") + } + +The `purchase` future is completed only once both `usdQuote` +and `chfQuote` are completed-- it depends on the values +of both these futures so its own computation cannot begin +earlier. + +The for-comprehension above is translated into: + + val purchase = usdQuote flatMap { + usd => + chfQuote + .withFilter(chf => isProfitable(usd, chf)) + .map(chf => connection.buy(amount, chf)) + } + +which is a bit harder to grasp than the for-comprehension, but +we analyze to better understand the `flatMap` operation. +The `flatMap` operation maps its own value into some other future. +Once this different future is completed, the resulting future +is completed with its value. +In our example, `flatMap` used the value of the `usdQuote` future +to map the value of the `chfQuote` into a third future which +sends a request to buy a certain amount of Swiss francs. +Only once this third future completes is the resulting future completed. + +This can be mind-boggling, but fortunately the `flatMap` operation +is seldom used outside for-comprehensions, which are easier to +understand. + +The `filter` combinator creates a new future which contains the value +of the original future only if it satisfies some predicate. Otherwise, +the new future is failed with a `NoSuchElementException`. For futures +calling `filter` has exactly the same effect as does calling `withFilter`. + +The relationship between the `collect` and `filter` combinator is similar +to the relationship of these methods in the collections API. + +It is important to note that calling the `foreach` combinator does not +block to traverse the value once it becomes available. +Instead, the function for the `foreach` gets asynchronously +executed only if the future is completed successfully. This means that +the `foreach` has exactly the same semantics as the `onSuccess` +callback. + +Since the `Future` trait can conceptually contain two types of values +(computation results and exceptions), there exists a need for +combinators which handle exceptions. + +Let's assume that based on the `rateQuote` we decide to buy a certain +amount. The `connection.buy` method takes an `amount` to buy and the expected +`quote`. It returns the amount bought. If the +`quote` has changed in the meanwhile, it will throw a +`QuoteChangedException` and it will not buy anything. If we want our +future to contain `0` instead of the exception, we use the `recover` +combinator: + + val purchase: Future[Int] = rateQuote map { + quote => connection.buy(amount, quote) + } recover { + case QuoteChangedException() => 0 + } + +The `recover` combinator creates a new future which holds the same +result as the original future if it completed successfully. If it did +not then the partial function argument is applied to the `Throwable` +which failed the original future. If it maps the `Throwable` to some +value, then the new future is successfully completed with that value. +If the partial function is not defined on that `Throwable`, then the +resulting future is failed with the same `Throwable`. + +The `recoverWith` combinator creates a new future which holds the +same result as the original future if it completed successfully. +Otherwise, the partial function is applied to the `Throwable` which +failed the original future. If it maps the `Throwable` to some future, +then this future is completed with the result of that future. +Its relation to `recover` is similar to that of `flatMap` to `map`. + +Combinator `fallbackTo` creates a new future which holds the result +of this future if it was completed successfully, or otherwise the +successful result of the argument future. In the event that both this +future and the argument future fail, the new future is completed with +the exception from this future, as in the following example which +tries to print US dollar value, but prints the Swiss franc value in +the case it fails to obtain the dollar value: + + val usdQuote = future { + connection.getCurrentValue(USD) + } map { + usd => "Value: " + usd + "$" + } + val chfQuote = future { + connection.getCurrentValue(CHF) + } map { + chf => "Value: " + chf + "CHF" + } + + val anyQuote = usdQuote fallbackTo chfQuote + + anyQuote onSuccess { println(_) } + +The `either` combinator creates a new future which either holds +the result of this future or the argument future, whichever completes +first, irregardless of success or failure. Here is an example in which +the quote which is returned first gets printed: + + val usdQuote = future { + connection.getCurrentValue(USD) + } map { + usd => "Value: " + usd + "$" + } + val chfQuote = future { + connection.getCurrentValue(CHF) + } map { + chf => "Value: " + chf + "CHF" + } + + val anyQuote = usdQuote either chfQuote + + anyQuote onSuccess { println(_) } + +The `andThen` combinator is used purely for side-effecting purposes. +It returns a new future with exactly the same result as the current +future, irregardless of whether the current future failed or not. +Once the current future is completed with the result, the closure +corresponding to the `andThen` is invoked and then the new future is +completed with the same result as this future. This ensures that +multiple `andThen` calls are ordered, as in the following example +which stores the recent posts from a social network to a mutable set +and then renders all the posts to the screen: + + val allposts = mutable.Set[String]() + + future { + session.getRecentPosts + } andThen { + posts => allposts ++= posts + } andThen { + posts => + clearAll() + for (post <- allposts) render(post) + } + +In summary, the combinators on futures are purely functional. +Every combinator returns a new future which is related to the +future it was derived from. + + +### Projections + +To enable for-comprehensions on a result returned as an exception, +futures also have projections. If the original future fails, the +`failed` projection returns a future containing a value of type +`Throwable`. If the original future succeeds, the `failed` projection +fails with a `NoSuchElementException`. The following is an example +which prints the exception to the screen: + + val f = future { + 2 / 0 + } + for (exc <- f.failed) println(exc) + +The following example does not print anything to the screen: + + val f = future { + 4 / 2 + } + for (exc <- f.failed) println(exc) + + + + + + + +### Extending Futures + +Support for extending the Futures API with additional utility methods is planned. +This will allow external frameworks to provide more specialized utilities. + +## Blocking + +As mentioned earlier, blocking on a future is strongly discouraged +for the sake of performance and for the prevention of deadlocks. +Callbacks and combinators on futures are a preferred way to use their results. +However, blocking may be necessary in certain situations and is supported by +the Futures and Promises API. + +In the currency trading example above, one place to block is at the +end of the application to make sure that all of the futures have been completed. +Here is an example of how to block on the result of a future: + + import scala.concurrent._ + import scala.concurrent.duration._ + + def main(args: Array[String]) { + val rateQuote = future { + connection.getCurrentValue(USD) + } + + val purchase = rateQuote map { quote => + if (isProfitable(quote)) connection.buy(amount, quote) + else throw new Exception("not profitable") + } + + Await.result(purchase, 0 nanos) + } + +In the case that the future fails, the caller is forwarded the +exception that the future is failed with. This includes the `failed` +projection-- blocking on it results in a `NoSuchElementException` +being thrown if the original future is completed successfully. + +Alternatively, calling `Await.ready` waits until the future becomes +completed, but does not retrieve its result. In the same way, calling +that method will not throw an exception if the future is failed. + +The `Future` trait implements the `Awaitable` trait with methods +method `ready()` and `result()`. These methods cannot be called directly +by the clients-- they can only be called by the execution context. + +To allow clients to call 3rd party code which is potentially blocking +and avoid implementing the `Awaitable` trait, the same +`blocking` primitive can also be used in the following form: + + blocking { + potentiallyBlockingCall() + } + +The blocking code may also throw an exception. In this case, the +exception is forwarded to the caller. + + + +## Exceptions + +When asynchronous computations throw unhandled exceptions, futures +associated with those computations fail. Failed futures store an +instance of `Throwable` instead of the result value. `Future`s provide +the `onFailure` callback method, which accepts a `PartialFunction` to +be applied to a `Throwable`. The following special exceptions are +treated differently: + +1. `scala.runtime.NonLocalReturnControl[_]` -- this exception holds a value +associated with the return. Typically, `return` constructs in method +bodies are translated to `throw`s with this exception. Instead of +keeping this exception, the associated value is stored into the future or a promise. + +2. `ExecutionException` - stored when the computation fails due to an +unhandled `InterruptedException`, `Error` or a +`scala.util.control.ControlThrowable`. In this case the +`ExecutionException` has the unhandled exception as its cause. These +exceptions are rethrown in the thread executing the failed +asynchronous computation. The rationale behind this is to prevent +propagation of critical and control-flow related exceptions normally +not handled by the client code and at the same time inform the client +in which future the computation failed. + + +## Promises + +So far we have only considered `Future` objects created through +asynchronous computations started using the `future` method. +However, futures can also be created using *promises*. + +While futures are defined as a type of read-only placeholder object +created for a result which doesn't yet exist, a promise can be thought +of as a writeable, single-assignment container, which completes a +future. That is, a promise can be used to successfully complete a +future with a value (by "completing" the promise) using the `success` +method. Conversely, a promise can also be used to complete a future +with an exception, by failing the promise, using the `failure` method. + +A promise `p` completes the future returned by `p.future`. This future +is specific to the promise `p`. Depending on the implementation, it +may be the case that `p.future eq p`. + +Consider the following producer-consumer example, in which one computation +produces a value and hands it off to another computation which consumes +that value. This passing of the value is done through a future. + + import scala.concurrent.{ future, promise } + import scala.concurrent.ExecutionContext.Implicits.global + + val p = promise[T] + val f = p.future + + val producer = future { + val r = produceSomething() + p success r + continueDoingSomethingUnrelated() + } + + val consumer = future { + startDoingSomething() + f onSuccess { + case r => doSomethingWithResult() + } + } + +Here, we create a promise and use its `future` method to obtain the +`Future` that it completes. Then, we begin two asynchronous +computations. The first does some computation, resulting in a value +`r`, which is then used to complete the future `f`, by fulfilling +the promise `p`. The second does some computation, and then reads the result `r` +of the completed future `f`. Note that the `consumer` can obtain the +result before the `producer` task is finished executing +the `continueDoingSomethingUnrelated()` method. + +As mentioned before, promises have single-assignment semantics. As +such, they can be completed only once. Calling `success` on a +promise that has already been completed (or failed) will throw an +`IllegalStateException`. + +The following example shows how to fail a promise. + + val p = promise[T] + val f = p.future + + val producer = future { + val r = someComputation + if (isInvalid(r)) + p failure (new IllegalStateException) + else { + val q = doSomeMoreComputation(r) + p success q + } + } + +Here, the `producer` computes an intermediate result `r`, and checks +whether it's valid. In the case that it's invalid, it fails the +promise by completing the promise `p` with an exception. In this case, +the associated future `f` is failed. Otherwise, the `producer` +continues its computation, and finally completes the future `f` with a +valid result, by completing promise `p`. + +Promises can also be completed with a `complete` method which takes +a potential value `Try[T]`-- either a failed result of type `Failure[Throwable]` or a +successful result of type `Success[T]`. + +Analogous to `success`, calling `failure` and `complete` on a promise that has already +been completed will throw an `IllegalStateException`. + +One nice property of programs written using promises with operations +described so far and futures which are composed through monadic +operations without side-effects is that these programs are +deterministic. Deterministic here means that, given that no exception +is thrown in the program, the result of the program (values observed +in the futures) will always be the same, irregardless of the execution +schedule of the parallel program. + +In some cases the client may want to complete the promise only if it +has not been completed yet (e.g., there are several HTTP requests being +executed from several different futures and the client is interested only +in the first HTTP response - corresponding to the first future to +complete the promise). For these reasons methods `tryComplete`, +`trySuccess` and `tryFailure` exist on future. The client should be +aware that using these methods results in programs which are not +deterministic, but depend on the execution schedule. + +The method `completeWith` completes the promise with another +future. After the future is completed, the promise gets completed with +the result of that future as well. The following program prints `1`: + + val f = future { 1 } + val p = promise[Int] + + p completeWith f + + p.future onSuccess { + case x => println(x) + } + +When failing a promise with an exception, three subtypes of `Throwable`s +are handled specially. If the `Throwable` used to break the promise is +a `scala.runtime.NonLocalReturnControl`, then the promise is completed with +the corresponding value. If the `Throwable` used to break the promise is +an instance of `Error`, `InterruptedException`, or +`scala.util.control.ControlThrowable`, the `Throwable` is wrapped as +the cause of a new `ExecutionException` which, in turn, is failing +the promise. + +Using promises, the `onComplete` method of the futures and the `future` construct +you can implement any of the functional composition combinators described earlier. +Let's assume you want to implement a new combinator `first` which takes +two futures `f` and `g` and produces a third future which is completed by either +`f` or `g` (whichever comes first), but only given that it is successful. +Here is an example of how to do it: + + def first[T](f: Future[T], g: Future[T]): Future[T] = { + val p = promise[T] + + f onSuccess { + case x => p.tryComplete(x) + } + + g onSuccess { + case x => p.tryComplete(x) + } + + p.future + } + + + + +## Utilities + +To simplify handling of time in concurrent applications `scala.concurrent` + will introduce a `Duration` abstraction. `Duration` is not supposed be yet another + general time abstraction. It is meant to be used with concurrency libraries and + will reside in `scala.concurrent` package. + +`Duration` is the base class representing length of time. It can be either finite or infinite. + Finite duration is represented with `FiniteDuration` class which is constructed from `Long` length and + `java.util.concurrent.TimeUnit`. Infinite durations, also extended from `Duration`, + exist in only two instances , `Duration.Inf` and `Duration.MinusInf`. Library also + provides several `Duration` subclasses for implicit conversion purposes and those should + not be used. + +Abstract `Duration` contains methods that allow : + +1. Conversion to different time units (`toNanos`, `toMicros`, `toMillis`, +`toSeconds`, `toMinutes`, `toHours`, `toDays` and `toUnit(unit: TimeUnit)`). +2. Comparison of durations (`<`, `<=`, `>` and `>=`). +3. Arithmetic operations (`+`, `-`, `*`, `/` and `unary_-`). +4. Minimum and maximum between `this` duration and the one supplied in the argument (`min`, `max`). +5. Check if the duration is finite (`finite_?`). + +`Duration` can be instantiated in the following ways: + +1. Implicitly from types `Int` and `Long`. For example `val d = 100 millis`. +2. By passing a `Long` length and a `java.util.concurrent.TimeUnit`. +For example `val d = Duration(100, MILLISECONDS)`. +3. By parsing a string that represent a time period. For example `val d = Duration("1.2 µs")`. + +Duration also provides `unapply` methods so it can be used in pattern matching constructs. +Examples: + + import scala.concurrent.Duration + import scala.concurrent.duration._ + import java.util.concurrent.TimeUnit._ + + // instantiation + val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit + val d2 = Duration(100, "millis") // from Long and String + val d3 = 100 millis // implicitly from Long, Int or Double + val d4 = Duration("1.2 µs") // from String + + // pattern matching + val Duration(length, unit) = 5 millis + + + From 52eba34a556c43b6be04573a590556dee2218aed Mon Sep 17 00:00:00 2001 From: Aleksandar Prokopec Date: Tue, 25 Sep 2012 11:23:26 +0200 Subject: [PATCH 2/2] Fix typos, rephrase stuff. --- overviews/futures/overview.md | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/overviews/futures/overview.md b/overviews/futures/overview.md index a09d1835ed..dbbf122152 100644 --- a/overviews/futures/overview.md +++ b/overviews/futures/overview.md @@ -41,8 +41,7 @@ environment to resize itself if necessary to guarantee progress. A `Future` is an object holding a value which may become available at some point. This value is usually the result of some other computation. Since this computation may fail with an exception, the `Future` -either holds a result of the computation or an exception in case -the computation failed. +may also hold an exception in case the computation throws one. Whenever a `Future` gets either a value or an exception, we say that the `Future` is **completed**. When a `Future` is completed with a value, we say that the future @@ -53,23 +52,23 @@ When a `Future` is completed with an exception, we say that the A `Future` has an important property that it may only be assigned once. Once a `Future` object is given a value or an exception, it becomes -in effect immutable-- it can never be overwritten with a new value. +in effect immutable-- it can never be overwritten. The simplest way to create a future object is to invoke the `future` method which starts an asynchronous computation and returns a future holding the result of that computation. The result becomes available once the future completes. -Note that `Future` is a type which denotes future objects, whereas +Note that `Future[T]` is a type which denotes future objects, whereas `future` is a method which creates and schedules an asynchronous computation, and then returns a future object which will be completed with the result of that computation. This is best shown through an example. -Let's assume that we want to use the hypothetical API of some +Let's assume that we want to use a hypothetical API of some popular social network to obtain a list of friends for a given user. -We will open a new session used to send requests and then send -the request to obtain a list of friends of a particular user: +We will open a new session and then send +a request to obtain a list of friends of a particular user: import scala.concurrent._ import ExecutionContext.Implicits.global @@ -100,7 +99,7 @@ responds. An unsuccessful attempt may result in an exception. In the following example, the `session` value is incorrectly -initialized, so the computation in the `future` block will yield a `NullPointerException`. +initialized, so the computation in the `future` block will throw a `NullPointerException`. This future `f` is then failed with this exception instead of being completed successfully: val session = null @@ -110,12 +109,12 @@ This future `f` is then failed with this exception instead of being completed su The line `import ExecutionContext.Implicits.global` above imports the default global execution context. -Execution contexts execute tasks submitted to them asynchronously. -You can think of execution contexts as thread pools. +Execution contexts execute tasks submitted to them, and +you can think of execution contexts as thread pools. They are essential for the `future` method because they handle how and when the asynchronous computation is executed. -You can define your own execution contexts and use them with `future` -as will be shown later, but for now it is sufficient to know that +You can define your own execution contexts and use them with `future`, +but for now it is sufficient to know that you can import the default execution context as shown above. Our example was based on a hypothetical social network API where @@ -174,7 +173,7 @@ value is a `Throwable`. Coming back to our social network example, let's assume we want to fetch a list of our own recent posts and render them to the screen. -We do so by calling a hypothetical method `getRecentPosts` which returns +We do so by calling a method `getRecentPosts` which returns a `List[String]`-- a list of recent textual posts: val f: Future[List[String]] = future { @@ -250,7 +249,7 @@ of the keyword to the screen: The `onComplete`, `onSuccess`, and `onFailure` methods have result type `Unit`, which means invocations of these methods cannot be chained. Note that this design is intentional, -to prevent suggesting that chained +to avoid suggesting that chained invocations may imply an ordering on the execution of the registered callbacks (callbacks registered on the same future are unordered). @@ -330,8 +329,8 @@ thus being eligible for GC. The callback mechanism we have shown is sufficient to chain future results with subsequent computations. -However, it is sometimes inconvenient as it may become bulky. -We show this through an example. Assume we have an API for +However, it is sometimes inconvenient and results in bulky code. +We show this with an example. Assume we have an API for interfacing with a currency trading service. Suppose we want to buy US dollars, but only when it's profitable. We first show how this could be done using callbacks: @@ -379,8 +378,8 @@ more straightforward composition. One of the basic combinators is `map`, which, given a future and a mapping function for the value of the future, produces a new future that is completed with the mapped value once the original future is successfully completed. -You can reason about `map`ping futures as you reason about `map`ping -collections. +You can reason about `map`ping futures in the same way you reason +about `map`ping collections. Let's rewrite the previous example using the `map` combinator: @@ -400,7 +399,7 @@ Let's rewrite the previous example using the `map` combinator: By using `map` on `rateQuote` we have eliminated one `onSuccess` callback and, more importantly, the nesting. If we now decide to sell some other currency, it suffices to use -`map` on `purchase`. +`map` on `purchase` again. But what happens if `isProfitable` returns `false`, hence causing an exception to be thrown? @@ -410,7 +409,7 @@ Furthermore, imagine that the connection was broken and that In that case we'd have no value to map, so the `purchase` would automatically be failed with the same exception as `rateQuote`. -More formally, if the original future is +In conclusion, if the original future is completed successfully then the returned future is completed with a mapped value from the original future. If the mapping function throws an exception the future is completed with that exception. If the @@ -457,18 +456,19 @@ The for-comprehension above is translated into: } which is a bit harder to grasp than the for-comprehension, but -we analyze to better understand the `flatMap` operation. +we analyze it to better understand the `flatMap` operation. The `flatMap` operation maps its own value into some other future. Once this different future is completed, the resulting future is completed with its value. -In our example, `flatMap` used the value of the `usdQuote` future +In our example, `flatMap` uses the value of the `usdQuote` future to map the value of the `chfQuote` into a third future which sends a request to buy a certain amount of Swiss francs. -Only once this third future completes is the resulting future completed. +The resulting future `purchase` is completed only once this third +future returned from `map` completes. This can be mind-boggling, but fortunately the `flatMap` operation is seldom used outside for-comprehensions, which are easier to -understand. +use and understand. The `filter` combinator creates a new future which contains the value of the original future only if it satisfies some predicate. Otherwise, @@ -715,7 +715,7 @@ in which future the computation failed. ## Promises -So far we have only considered `Future` objects created through +So far we have only considered `Future` objects created by asynchronous computations started using the `future` method. However, futures can also be created using *promises*. @@ -733,7 +733,7 @@ may be the case that `p.future eq p`. Consider the following producer-consumer example, in which one computation produces a value and hands it off to another computation which consumes -that value. This passing of the value is done through a future. +that value. This passing of the value is done using a promise. import scala.concurrent.{ future, promise } import scala.concurrent.ExecutionContext.Implicits.global