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.
Monte provides user-controllable transparent proxy objects, promises, for highly customized asynchronous workflows.
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()
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
# 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)
q is another promise. It will be resolved automatically,
p resolves, with the value that
p returned from its
sent message; in this case, if
q would be
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.