An expression can appear delayed as do e
or 'e
, which are both the same as _ -> e
and () -> e
. If e
has type T
, then do e
and 'e
both have the type forall a. a -> T
.
If c
is a delayed computation, it can be forced with c()
or !c
. The expression c
must conform to a type () -> t
for some type t
, in which case !c
has type t
.
Delayed computations are important for writing expressions that require abilities. For example:
This example defines a small I/O program. The type {IO, Exception} ()
by itself is not allowed as the type of a top-level definition, since the IO
and Exception
abilities must be provided by a handler, see abilities and ability handlers. Instead, program
has the type program : '{IO, Exception} ()
(note the '
indicating a delayed computation). Inside a handler for IO
, this computation can be forced with !program
.
Inside the program, !readLine
has to be forced, as the type of readLine
is readLine : '{IO, Exception} Text
, a delayed computation which, when forced, reads a line from standard input.
Syntactic precedence
The reserved symbols '
and !
bind more tightly than function application, So 'f x
is the same as (_ -> f) x
and !x + y
is the same as (x ()) + y
.
These symbols bind less tightly than keywords that introduce blocks, so do x
and 'let x
are both the same as _ -> let x
and !if b then p else q
is the same as (if b then p else q) ()
.
Additional '
and !
combine in the following way:
do do syntacticPrecedence.x
is the same as(_ -> (_ -> x))
or(_ _ -> x)
.!!x
is the same asx () ()
.!'x
and'!x
are both the same asx
.
You can use parentheses to precisely control how '
and !
get applied.