One of the main novel concepts relative to SML are futures. Futures in Alice bring the basic idea of "logic variables" as found in logic programming languages into the typed functional world of ML.
Futures are provided through the library structure Future:
structure Future: sig exception Future of exn exception Cyclic val concur: (unit -> 'a) -> 'a val byneed: (unit -> 'a) -> 'a val alarm: Time.time -> unit val await: 'a -> 'a val awaitOne: 'a * 'b -> 'a val isFuture: 'a -> bool val isFailed: 'a -> bool end
This structure provides three basic primitives to create futures. The operation
val concur: (unit -> 'a) -> 'a
applies the procedure in a new thread. It immediately returns a future associated with that thread. When the procedure terminates with a result that is different from the future, this future is globally replaced with the result. If the application terminates with an exception e, the future is marked as failed and all operations accessing it will raise Future(e). If the application terminates returning the future itself, the future is marked as failed and all operations accessing it will raise Future(Cyclic).
The operation
val byneed: (unit -> 'a) -> 'a
returns a by-need future. As soon as a thread blocks on the future, the argument procedure is applied in a new thread. Evaluation proceeds similar to concur. By-need futures can be used for lazy evaluation.
Both these operations are in the top-level environment and can thus be used unqualified.
Finally, the operation
val alarm: Time.time -> unit
creates a future that will be replaced by the value () after the given period of time (see structure Time). This is useful for programming timeouts and the like.
The operation
val await: 'a -> 'a
is the identity for all non-future arguments. On futures await blocks until the future is replaced by a proper value. Similarly,
val awaitOne: 'a * 'b -> 'a
blocks until at least one if its arguments is a proper value. It then returns the first argument.
Every future is in one of three possible states:
A freshly created future is undetermined. When it is replaced by a proper value it becomes succeeded. If the computation associated with a future created by concur or byneed is terminated with an exception, the future becomes failed.
The operation
val isFuture: 'a -> bool
tests whether its argument is an undetermined future. It does not block. Similarly,
val isFailed: 'a -> bool
tests whether its argument is a failed future.
Note: In Operette 1, the operation isFuture does return true for failed futures, due to limitations of the Mozart virtual machine. Similarly, isFailed will always deliver false.
A forth form of future is introduced by the structure Promise:
structure Promise: sig type 'a promise exception Promise val promise: unit -> 'a promise val future: 'a promise -> 'a val fulfill: 'a promise * 'a -> unit val fail: 'a promise * exn -> unit end
A promise is a handle for a future. The operation
val promise: unit -> 'a promise
creates a promise and thereby a fresh future. The operation
val future: 'a promise -> 'a
returns the future associated with the promise. This future can become succeeded by applying
val fulfill: 'a promise * 'a -> unit
to the promise. It globally replaces the future with the right argument, provided the left and right argument are not variants of the same future. If the promise has already been fulfilled (or failed, see below), the exception Promise is raised. If the left and right arguments are variants of the same future the exception Cyclic is raised.
Dually, a promise and its future can be explicitly failed by applying
val fail: 'a promise * exn -> unit
to the promise. If a promise is failed with exception e, any subsequent attempt to access its future will cause the exception Future(e) to be raised. If the promise already had been fulfilled or failed, fail will raise the exception Promise.
For concurrent programming, there exists a concurrent variant of ML references in the structure Cell:
structure Cell: sig type 'a cell val cell: 'a -> 'a cell val exchange: 'a cell * 'a -> 'a end
The operation cell creates a new cell and initializes it with the given value. With exchange, the content of a cell can be replaced by a new value. The old value is returned. The exchange operation is atomic, and can thus be used for synchronisation.