Promises¶
Promises are a great way of dealing with eventual values, allowing one to compose and synchronise processes that depend on values that are computed asynchronously.
—Quil
Monte provides user-controllable transparent proxy objects, promises, for highly customized asynchronous workflows.
Basic Promises¶
The basic usage of promises is to create a pair of objects, called the promise and the resolver:
# Traditionally, promises are named "p" and resolvers are named "r".
def [p, r] := Ref.promise()
The Ref
object in the safe scope can produce promise/resolver pairs. It
also has many utility methods for manipulating promises.
A promise is a transparent proxy; it does not expose its own behavior via
message passing, but instead forwards all received messages to another object.
Instead, the resolver and Ref
object coordinate to control the behavior of
the promise:
# This next line will throw an exception; the promise isn't yet resolved,
# so it can't deliver this immediate call.
p.add(5)
# We can resolve the promise, at which point the promise will forward
# immediate calls to its resolved value.
r.resolve(7)
# And now we succeed!
p.add(12)
Promises do not just resolve; they can also break. A broken promise will never resolve, but instead refers to a problem, which is an object (often a string) describing a failure.
# Here we create a promise...
def [p, r] := Ref.promise()
# And now we break the promise!
r.smash(`Promise was broken, sorry!`)
# Referencing or using the promise will throw...
p.add(12)
# ...but some operations are still safe.
Ref.optProblem(p)
When-expressions and Delayed Actions¶
Promises are commonly used to perform delayed actions which will execute at some later time.
To queue an action, use an eventual send:
# This message will be delivered on some later turn.
def q := p<-add(5)
What is q
? q
is another promise. It will be resolved automatically,
sometime after p
resolves, with the value that p
returned from its
sent message; in this case, if p
was 7
, then q
would be 12
.
Suppose that the action that we want to enqueue is more complex than a single passed message. In that case, Monte provides the when-expression:
# When the promise resolves, notify the user and start the next section.
when (p) ->
traceln(`Attention user: The promise $p has resolved.`)
# This funny-looking syntax means to use the default verb of "run",
# just like with a normal call.
nextSection<-()
catch problem:
# Something went wrong. Better notify the user.
traceln(`Attention user: There was a problem: $problem`)
nextSection<-failed()
The when-expression consists of a when-block and an optional catch-block. When the promise given to the when-expression becomes resolved, the when-block will run on its own turn; if the promise is broken, then the catch-block will run instead.