Futures and promises explained

Futures and promises should not be confused with Promise theory.

In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is not yet complete.

The term promise was proposed in 1976 by Daniel P. Friedman and David Wise,[1] and Peter Hibbard called it eventual.[2] A somewhat similar concept future was introduced in 1977 in a paper by Henry Baker and Carl Hewitt.[3]

The terms future, promise, delay, and deferred are often used interchangeably, although some differences in usage between future and promise are treated below. Specifically, when usage is distinguished, a future is a read-only placeholder view of a variable, while a promise is a writable, single assignment container which sets the value of the future. Notably, a future may be defined without specifying which specific promise will set its value, and different possible promises may set the value of a given future, though this can be done only once for a given future. In other cases a future and a promise are created together and associated with each other: the future is the value, the promise is the function that sets the value – essentially the return value (future) of an asynchronous function (promise). Setting the value of a future is also called resolving, fulfilling, or binding it.

Applications

Futures and promises originated in functional programming and related paradigms (such as logic programming) to decouple a value (a future) from how it was computed (a promise), allowing the computation to be done more flexibly, notably by parallelizing it. Later, it found use in distributed computing, in reducing the latency from communication round trips. Later still, it gained more use by allowing writing asynchronous programs in direct style, rather than in continuation-passing style.

Implicit vs. explicit

Use of futures may be implicit (any use of the future automatically obtains its value, as if it were an ordinary reference) or explicit (the user must call a function to obtain the value, such as the get method of in Java). Obtaining the value of an explicit future can be called stinging or forcing. Explicit futures can be implemented as a library, whereas implicit futures are usually implemented as part of the language.

The original Baker and Hewitt paper described implicit futures, which are naturally supported in the actor model of computation and pure object-oriented programming languages like Smalltalk. The Friedman and Wise paper described only explicit futures, probably reflecting the difficulty of efficiently implementing implicit futures on stock hardware. The difficulty is that stock hardware does not deal with futures for primitive data types like integers. For example, an add instruction does not know how to deal with 3 + ''future '' factorial(100000). In pure actor or object languages this problem can be solved by sending ''future '' factorial(100000) the message +[3], which asks the future to add 3 to itself and return the result. Note that the message passing approach works regardless of when factorial(100000) finishes computation and that no stinging/forcing is needed.

Promise pipelining

The use of futures can dramatically reduce latency in distributed systems. For instance, futures enable promise pipelining,[4] [5] as implemented in the languages E and Joule, which was also called call-stream[6] in the language Argus.

Consider an expression involving conventional remote procedure calls, such as:

 t3 := (x.a).c(y.b)
which could be expanded to
 t1 := x.a;
 t2 := y.b;
 t3 := t1.c(t2);
Each statement needs a message to be sent and a reply received before the next statement can proceed. Suppose, for example, that x, y, t1, and t2 are all located on the same remote machine. In this case, two complete network round-trips to that machine must take place before the third statement can begin to execute. The third statement will then cause yet another round-trip to the same remote machine.

Using futures, the above expression could be written

 t3 := (x <- a) <- c(y <- b)

which could be expanded to

 t1 := x <- a;
 t2 := y <- b;
 t3 := t1 <- c(t2);

The syntax used here is that of the language E, where x <- a means to send the message a asynchronously to x. All three variables are immediately assigned futures for their results, and execution proceeds to subsequent statements. Later attempts to resolve the value of t3 may cause a delay; however, pipelining can reduce the number of round-trips needed. If, as in the prior example, x, y, t1, and t2 are all located on the same remote machine, a pipelined implementation can compute t3 with one round-trip instead of three. Because all three messages are destined for objects which are on the same remote machine, only one request need be sent and only one response need be received containing the result. The send t1 <- c(t2) would not block even if t1 and t2 were on different machines to each other, or to x or y.

Promise pipelining should be distinguished from parallel asynchronous message passing. In a system supporting parallel message passing but not pipelining, the message sends x <- a and y <- b in the above example could proceed in parallel, but the send of t1 <- c(t2) would have to wait until both t1 and t2 had been received, even when x, y, t1, and t2 are on the same remote machine. The relative latency advantage of pipelining becomes even greater in more complicated situations involving many messages.

Promise pipelining also should not be confused with pipelined message processing in actor systems, where it is possible for an actor to specify and begin executing a behaviour for the next message before having completed processing of the current message.

Read-only views

In some programming languages such as Oz, E, and AmbientTalk, it is possible to obtain a read-only view of a future, which allows reading its value when resolved, but does not permit resolving it:

Support for read-only views is consistent with the principle of least privilege, since it enables the ability to set the value to be restricted to subjects that need to set it. In a system that also supports pipelining, the sender of an asynchronous message (with result) receives the read-only promise for the result, and the target of the message receives the resolver.

Thread-specific futures

Some languages, such as Alice ML, define futures that are associated with a specific thread that computes the future's value. This computation can start either eagerly when the future is created, or lazily when its value is first needed. A lazy future is similar to a thunk, in the sense of a delayed computation.

Alice ML also supports futures that can be resolved by any thread, and calls these promises. This use of promise is different from its use in E as described above. In Alice, a promise is not a read-only view, and promise pipelining is unsupported. Instead, pipelining naturally happens for futures, including ones associated with promises.

Blocking vs non-blocking semantics

If the value of a future is accessed asynchronously, for example by sending a message to it, or by explicitly waiting for it using a construct such as when in E, then there is no difficulty in delaying until the future is resolved before the message can be received or the wait completes. This is the only case to be considered in purely asynchronous systems such as pure actor languages.

However, in some systems it may also be possible to attempt to immediately or synchronously access a future's value. Then there is a design choice to be made:

As an example of the first possibility, in C++11, a thread that needs the value of a future can block until it is available by calling the wait or get member functions. A timeout can also be specified on the wait using the wait_for or wait_until member functions to avoid indefinite blocking. If the future arose from a call to std::async then a blocking wait (without a timeout) may cause synchronous invocation of the function to compute the result on the waiting thread.

Related constructs

Futures are a particular case of the synchronization primitive "events," which can be completed only once. In general, events can be reset to initial empty state and, thus, completed as many times as desired.[7]

An I-var (as in the language Id) is a future with blocking semantics as defined above. An I-structure is a data structure containing I-vars. A related synchronization construct that can be set multiple times with different values is called an M-var. M-vars support atomic operations to take or put the current value, where taking the value also sets the M-var back to its initial empty state.

A concurrent logic variable is similar to a future, but is updated by unification, in the same way as logic variables in logic programming. Thus it can be bound more than once to unifiable values, but cannot be set back to an empty or unresolved state. The dataflow variables of Oz act as concurrent logic variables, and also have blocking semantics as mentioned above.

A concurrent constraint variable is a generalization of concurrent logic variables to support constraint logic programming: the constraint may be narrowed multiple times, indicating smaller sets of possible values. Typically there is a way to specify a thunk that should run whenever the constraint is narrowed further; this is needed to support constraint propagation.

Relations between the expressiveness of different forms of future

Eager thread-specific futures can be straightforwardly implemented in non-thread-specific futures, by creating a thread to calculate the value at the same time as creating the future. In this case it is desirable to return a read-only view to the client, so that only the newly created thread is able to resolve this future.

To implement implicit lazy thread-specific futures (as provided by Alice ML, for example) in terms in non-thread-specific futures, needs a mechanism to determine when the future's value is first needed (for example, the WaitNeeded construct in Oz). If all values are objects, then the ability to implement transparent forwarding objects is sufficient, since the first message sent to the forwarder indicates that the future's value is needed.

Non-thread-specific futures can be implemented in thread-specific futures, assuming that the system supports message passing, by having the resolving thread send a message to the future's own thread. However, this can be viewed as unneeded complexity. In programming languages based on threads, the most expressive approach seems to be to provide a mix of non-thread-specific futures, read-only views, and either a WaitNeeded construct, or support for transparent forwarding.

Evaluation strategy

The evaluation strategy of futures, which may be termed call by future, is non-deterministic: the value of a future will be evaluated at some time between when the future is created and when its value is used, but the precise time is not determined beforehand and can change from run to run. The computation can start as soon as the future is created (eager evaluation) or only when the value is actually needed (lazy evaluation), and may be suspended part-way through, or executed in one run. Once the value of a future is assigned, it is not recomputed on future accesses; this is like the memoization used in call by need.

A is a future that deterministically has lazy evaluation semantics: the computation of the future's value starts when the value is first needed, as in call by need. Lazy futures are of use in languages which evaluation strategy is by default not lazy. For example, in C++11 such lazy futures can be created by passing the std::launch::deferred launch policy to std::async, along with the function to compute the value.

Semantics of futures in the actor model

In the actor model, an expression of the form ''future'' <Expression> is defined by how it responds to an Eval message with environment E and customer C as follows: The future expression responds to the Eval message by sending the customer C a newly created actor F (the proxy for the response of evaluating <Expression>) as a return value concurrently with sending <Expression> an Eval message with environment E and customer C. The default behavior of F is as follows:

However, some futures can deal with requests in special ways to provide greater parallelism. For example, the expression 1 + future factorial(n) can create a new future that will behave like the number 1+factorial(n). This trick does not always work. For example, the following conditional expression:

''if'' m>future factorial(n) ''then'' print("bigger") ''else'' print("smaller")suspends until the future for factorial(n) has responded to the request asking if m is greater than itself.

History

The future and/or promise constructs were first implemented in programming languages such as MultiLisp and Act 1. The use of logic variables for communication in concurrent logic programming languages was quite similar to futures. These began in Prolog with Freeze and IC Prolog, and became a true concurrency primitive with Relational Language, Concurrent Prolog, guarded Horn clauses (GHC), Parlog, Strand, Vulcan, Janus, Oz-Mozart, Flow Java, and Alice ML. The single-assignment I-var from dataflow programming languages, originating in Id and included in Reppy's Concurrent ML, is much like the concurrent logic variable.

The promise pipelining technique (using futures to overcome latency) was invented by Barbara Liskov and Liuba Shrira in 1988,[6] and independently by Mark S. Miller, Dean Tribble and Rob Jellinghaus in the context of Project Xanadu circa 1989.

The term promise was coined by Liskov and Shrira, although they referred to the pipelining mechanism by the name call-stream, which is now rarely used.

Both the design described in Liskov and Shrira's paper, and the implementation of promise pipelining in Xanadu, had the limit that promise values were not first-class: an argument to, or the value returned by a call or send could not directly be a promise (so the example of promise pipelining given earlier, which uses a promise for the result of one send as an argument to another, would not have been directly expressible in the call-stream design or in the Xanadu implementation). It seems that promises and call-streams were never implemented in any public release of Argus, the programming language used in the Liskov and Shrira paper. Argus development stopped around 1988. The Xanadu implementation of promise pipelining only became publicly available with the release of the source code for Udanax Gold in 1999, and was never explained in any published document. The later implementations in Joule and E support fully first-class promises and resolvers.

Several early actor languages, including the Act series,[8] [9] supported both parallel message passing and pipelined message processing, but not promise pipelining. (Although it is technically possible to implement the last of these features in the first two, there is no evidence that the Act languages did so.)

After 2000, a major revival of interest in futures and promises occurred, due to their use in responsiveness of user interfaces, and in web development, due to the request–response model of message-passing. Several mainstream languages now have language support for futures and promises, most notably popularized by FutureTask in Java 5 (announced 2004)[10] and the async/await constructions in .NET 4.5 (announced 2010, released 2012)[11] [12] largely inspired by the asynchronous workflows of F#,[13] which dates to 2007.[14] This has subsequently been adopted by other languages, notably Dart (2014), Python (2015),[15] Hack (HHVM), and drafts of ECMAScript 7 (JavaScript), Scala, and C++ (2011).

List of implementations

Some programming languages are supporting futures, promises, concurrent logic variables, dataflow variables, or I-vars, either by direct language support or in the standard library.

List of concepts related to futures and promises by programming language

Languages also supporting promise pipelining include:

List of library-based implementations of futures

Coroutines

Futures can be implemented in coroutines[15] or generators,[92] resulting in the same evaluation strategy (e.g., cooperative multitasking or lazy evaluation).

Channels

See main article: Channel (programming). Futures can easily be implemented in channels: a future is a one-element channel, and a promise is a process that sends to the channel, fulfilling the future.[93] [94] This allows futures to be implemented in concurrent programming languages with support for channels, such as CSP and Go. The resulting futures are explicit, as they must be accessed by reading from the channel, rather than only evaluation.

See also

External links

Notes and References

  1. Daniel. Friedman. David Wise. 1976. The Impact of Applicative Programming on Multiprocessing. International Conference on Parallel Processing. 263–272.
    Preliminary version of: Aspects of Applicative Programming for Parallel Processing . IEEE Transactions on Computers . April 1978 . C-27 . 4 . 289–296 . 10.1109/tc.1978.1675100. Daniel. Friedman. David. Wise. 10.1.1.295.9692. 16333366 .
  2. Peter. Hibbard. 1976. Parallel Processing Facilities. New Directions in Algorithmic Languages, (ed.) Stephen A. Schuman, IRIA, 1976..
  3. Henry Baker. Carl Hewitt. The Incremental Garbage Collection of Processes. Proceedings of the Symposium on Artificial Intelligence Programming Languages. ACM SIGPLAN Notices 12, 8. August 1977. 55–59. 13 February 2015. 4 July 2008. https://web.archive.org/web/20080704132429/http://home.pipeline.com/~hbaker1/Futures.html. dead.
  4. http://www.erights.org/elib/distrib/pipeline.html Promise Pipelining at erights.org
  5. http://c2.com/cgi/wiki?PromisePipelining Promise Pipelining on the C2 wiki
  6. Barbara Liskov . Liuba Shrira . Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems. 1988. 10.1145/53990.54016. Proceedings of the SIGPLAN '88 Conference on Programming Language Design and Implementation; Atlanta, Georgia, United States. 260–267. 0-89791-269-1. ACM. Also published in ACM SIGPLAN Notices 23(7).
  7. http://aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html#fn13 500 lines or less, "A Web Crawler With asyncio Coroutines" by A. Jesse Jiryu Davis and Guido van Rossum
  8. Henry Lieberman . A Preview of Act 1 . June 1981 . MIT AI Memo 625.
  9. Henry Lieberman . Thinking About Lots of Things at Once without Getting Confused: Parallelism in Act 1 . June 1981 . MIT AI Memo 626.
  10. Web site: Concurrency in JDK 5.0. Goetz. Brian. . 23 November 2004.
  11. Web site: Async in 4.5: Worth the Await – .NET Blog – Site Home – MSDN Blogs . Blogs.msdn.com . 2014-05-13.
  12. Web site: Asynchronous Programming with Async and Await (C# and Visual Basic) . Msdn.microsoft.com . 2014-05-13.
  13. Web site: Asynchronous C# and F# (I.): Simultaneous introduction. 29 October 2010. Tomas Petricek.
  14. Web site: The F# Asynchronous Programming Model, PADL 2011. 21 October 2010 . Don Syme. Tomas Petricek. Dmitry Lomov.
  15. Web site: PEP 0492 – Coroutines with async and await syntax.
  16. Kenjiro Taura . Satoshi Matsuoka . Akinori Yonezawa . 1994. ABCL/f: A Future-Based Polymorphic Typed Concurrent Object-Oriented Language – Its Design and Implementation.. In Proceedings of the DIMACS workshop on Specification of Parallel Algorithms, number 18 in Dimacs Series in Discrete Mathematics and Theoretical Computer Science. 275–292. 10.1.1.23.1161. American Mathematical Society.
  17. Web site: Dart SDK dart async Completer.
  18. Web site: Dart Language Asynchrony Support: Phase 1. Gilad Bracha . October 2014.
  19. Web site: Task.
  20. Web site: Steve Dekorte. 2005. Io, The Programming Language.
  21. Web site: Using promises. Mozilla Developer Network. 23 February 2021.
  22. Web site: Making asynchronous programming easier with async and await. Mozilla Developer Network. 23 February 2021.
  23. Web site: Rich Hickey. 2009. changes.txt at 1.1.x from richhickey's clojure. GitHub.
  24. Web site: Future – Kotlin Programming Language.
  25. Web site: Tutorial of Oz . Mozart Global User Library . 12 April 2011 . Seif Haridi . Nils Franzen . 14 May 2011 . https://web.archive.org/web/20110514010946/http://www.mozart-oz.org/documentation/tutorial/node1.html . dead .
  26. https://www.python.org/download/releases/3.2/ Python 3.2 Release
  27. https://www.python.org/downloads/release/python-350/ Python 3.5 Release
  28. Web site: Parallelism with Futures. PLT. 2 March 2012.
  29. Web site: class Promise. raku.org. 2022-08-19.
  30. Web site: Future in std::future - Rust . 2023-12-16 . doc.rust-lang.org.
  31. https://orthecreedence.github.io/blackbird/ Common Lisp Blackbird
  32. http://common-lisp.net/project/eager-future/ Common Lisp Eager Future2
  33. https://lparallel.org Lisp in parallel – A parallel programming library for Common Lisp
  34. http://marijnhaverbeke.nl/pcall/ Common Lisp PCall
  35. Web site: Chapter 30. Thread 4.0.0 . 26 June 2013.
  36. Web site: Dlib C++ Library #thread_pool . 26 June 2013.
  37. Web site: GitHub – facebook/folly: An open-source C++ library developed and used at Facebook.. GitHub. 2019-01-08.
  38. Web site: HPX. 2019-02-10.
  39. Web site: Threads Slides of POCO.
  40. Web site: QtCore 5.0: QFuture Class . Qt Project . 26 June 2013 . https://web.archive.org/web/20130601062401/http://qt-project.org/doc/qt-5.0/qtcore/qfuture.html . 1 June 2013 . dead .
  41. Web site: Seastar . Seastar project . 22 August 2016.
  42. Web site: stlab is the ongoing work of what was Adobe's Software Technology Lab. The Adobe Source Libraries (ASL), Platform Libraries, and new stlab libraries are hosted on github.. 2021-01-31.
  43. http://gpars.codehaus.org Groovy GPars
  44. http://cujojs.com Cujo.js
  45. https://github.com/cujojs/when JavaScript when.js
  46. http://promisesaplus.com Promises/A+ specification
  47. http://dojotoolkit.org/documentation/tutorials/1.6/promises/ promises
  48. https://mochi.github.io/mochikit/doc/html/MochiKit/Async.html JavaScript MochKit.Async
  49. https://angularjs.org/ JavaScript Angularjs
  50. https://github.com/kriszyp/node-promise JavaScript node-promise
  51. Web site: JavaScript Q . 8 April 2013 . 31 December 2018 . https://web.archive.org/web/20181231093236/http://documentup.com/kriskowal/q . dead .
  52. https://github.com/tildeio/rsvp.js JavaScript RSVP.js
  53. http://yuilibrary.com/ YUI JavaScript class library
  54. http://yuilibrary.com/yui/docs/promise/ YUI JavaScript promise class
  55. https://github.com/petkaantonov/bluebird JavaScript Bluebird
  56. http://jdeferred.org Java JDeferred
  57. https://github.com/linkedin/parseq/wiki Java ParSeq
  58. https://github.com/mikeash/MAFuture Objective-C MAFuture GitHub
  59. http://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html Objective-C MAFuture mikeash.com
  60. https://github.com/couchdeveloper/RXPromise Objective-C RXPromise
  61. https://github.com/Strilanc/ObjC-CollapsingFutures ObjC-CollapsingFutures
  62. http://promisekit.org/ Objective-C PromiseKit
  63. https://github.com/mproberts/objc-promise Objective-C objc-promise
  64. https://github.com/oleganza/OAPromise Objective-C OAPromise
  65. http://caml.inria.fr/pub/docs/manual-ocaml/libref/Lazy.html OCaml Lazy
  66. http://metacpan.org/module/Future Perl Future
  67. https://metacpan.org/pod/Promises Perl Promises
  68. http://metacpan.org/module/Reflex/ Perl Reflex
  69. http://metacpan.org/module/Promise::ES6/ Perl Promise::ES6
  70. Web site: Promise::XS – Fast promises in Perl – metacpan.org. 2021-02-14. metacpan.org.
  71. https://github.com/reactphp/promise PHP React/Promise
  72. https://docs.python.org/3/library/asyncio-task.html#future Python built-in implementation
  73. http://code.google.com/p/pythonfutures/ pythonfutures
  74. Web site: Twisted Deferreds . 29 April 2010 . 6 August 2020 . https://web.archive.org/web/20200806202718/https://twistedmatrix.com/documents/current/core/howto/defer.html . dead .
  75. https://cran.r-project.org/package=future R package future
  76. https://cran.r-project.org/package=future future
  77. https://github.com/ruby-concurrency/concurrent-ruby Concurrent Ruby
  78. http://rubygems.org/gems/promise Ruby Promise gem
  79. https://github.com/cotag/libuv Ruby libuv
  80. Web site: Ruby Celluloid gem . 19 February 2022 . 8 May 2013 . https://web.archive.org/web/20130508122056/http://celluloid.io/ . dead .
  81. https://github.com/adhearsion/future-resource Ruby future-resource
  82. https://github.com/alexcrichton/futures-rs futures-rs crate
  83. https://twitter.github.io/util/ Twitter's util library
  84. Web site: Swift Async . 23 June 2014 . 31 December 2018 . https://web.archive.org/web/20181231092935/https://bitbucket.org/al45tair/async . dead .
  85. https://github.com/FutureKit/FutureKit Swift FutureKit
  86. https://developer.apple.com/library/prerelease/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/ Swift Apple GCD
  87. https://github.com/couchdeveloper/FutureLib Swift FutureLib
  88. https://github.com/bignerdranch/deferred bignerdranch/Deferred
  89. https://github.com/Thomvis/BrightFutures Thomvis/BrightFutures
  90. https://github.com/belozierov/SwiftCoroutine belozierov/SwiftCoroutine
  91. https://sourceforge.net/projects/tcl-promise/ tcl-promise
  92. https://esdiscuss.org/topic/does-async-await-solve-a-real-problem#content-2 Does async/await solve a real problem?
  93. Web site: Go language patterns Futures . 9 February 2014 . 4 December 2020 . https://web.archive.org/web/20201204060631/https://sites.google.com/site/gopatterns/concurrency/futures . dead .
  94. Web site: Go Language Patterns . 9 February 2014 . 11 November 2020 . https://web.archive.org/web/20201111222255/https://sites.google.com/site/gopatterns . dead .