alice
manual.


Alice Project

syntactic
enhancements


________ Overview ____________________________________________________

The extensions listed here are mostly syntactic sugar that is also expressible by other, less convenient means:


________ Lexical syntax extensions ___________________________________

Line comments

The comment marker (*) introduces a comment that stretches to the end of line:

fun myf x = bla    (*) my function
fun myg x = blo    (*) my second function

Line comments properly nest into conventional comments, so the following is one single comment, even though the inner line comment contains a closing comment delimiter:

(*
fun myf x = bla    (*) my function *)
*)

Literals

Numeric literals may contain underscores to group digits:

val pi = 3.141_592_653_596
val billion = 1_000_000_000
val nibbles = 0wx_f300_4588

Moreover, binary integer and word literals are supported:

val ten  = 0b1010
val bits = 0wb1101_0010_1111_0010

Long identifiers

Long identifiers are not part of the lexical syntax, but of the context-free grammar. Consequently, there may be arbitrary white space separating the dots from the identifiers:

mod . submod (*Here!*) . subsub
    . f

Source position

For debugging purposes, Alice supports two special expression derived forms:

_file_
_line_

Occurrences of these keywords are replaced by the file name and the line number of their respective occurrence in the source file. For example,

print ("Error in file " ^ _file_ ", line " ^ Int.toString _line_ ^ "\n")

________ Vectors _____________________________________________________

Following SML/NJ, Alice provides vector expressions and patterns:

val v = #[1, 2, 4, 1, 2]

fun f #[]  = 0
  | f #[n] = n
  | f  v   = 1

Vector syntax

atexp ::= ...
#[ exp1 , ... , expn ] vector (n≥0)
atpat ::= ...
#[ pat1 , ... , patn ] vector (n≥0)

________ Records _____________________________________________________

Record punning

While SML allows punning in record patterns (so that the left hand side of the former example is legal), it does not allow punning in record expressions. In Alice, the latter is also available as a simple derived form, dualing the derived form for patterns. For example, the declaration

fun f {a,b,c} = {a,b}

is understood as an abbreviation for

fun f {a = a, b = b, c = c} = {a = a, b = b}

The labels may have type annotations, i.e. {a : int, b} abbreviates {a = a : int, b = b}.

Record extension

Ellipses in a record pattern may be followed by a pattern that will be matched against the "remaining" record. For example,

val r = {a = 1, b = true, c = "hello"}
val {a = n, ... = r'} = r

will bind r' to the record {b=true, c="hello"}.

Analogously, records can be constructed as extension of other records using ellipses:

val r'' = {d = 3.14, ... = r'}

This example will bind r'' to the record {b=true, c="hello", d=3.14}. Note that the ellipses expression must have record type, but this record may not contain any of the added labels.

Record extension can also be used in type expressions:

type 'a t = {a : 'a, b : bool}
type u = {c : char, d : string, ... : int t}

Now the type u is equivalent to {a:int, b:bool, c:char, d:string}. Again, the label sets of the ellipses type and the explicit fields must be disjoint.

In all three forms, the ellipses may appear anywhere in the row, but a row may not have multiple occurences of ellipses.

Functional record update

There also is syntax for functional record update. For example,

let
    val r = {a = 1, b = true, c = "hello"}
in
    {r where a = 3, c = "bye"}
end

evaluates to

{a = 3, b = true, c = "bye"}

Here, the updated record must have a type containing all labels being updated. However, the types of these fields may change with the update.

Record syntax

atexp ::= ...
{ atexp where exprow } record update
exprow ::= ...
vid <: ty> <, exprow> label as expression
... = exp <, exprow> ellipses
patrow ::= ...
... <= pat> <, patrow> ellipses
tyrow ::= ...
... : ty <, tyrow> ellipses

Labels as expressions are a derived form. Likewise is record update:

vid <: ty> <, exprow> ==> vid = vid <: ty> <, exprow>
{atexp where exprow} ==> let val {patrow, ... = vid} = atexp
in {exprow, ... = vid} end

In the rewritten form of record update, vid is a fresh identifier, and patrow is obtained from exprow by replacing all right-hand sides with wildcards.


________ Patterns ____________________________________________________

Alternative patterns

Alternative patterns (also called or patterns) are present in SML/NJ as well and allow more compact case analysis:

fun fac (0|1) = 1
  | fac   n   = n * fac(n-1)

The syntax subsumes multiple or-patterns and multiple matches:

case exp of
  | A | B | C => 1
  | D | E     => 2

The patterns nested inside an alternative pattern may bind variables, but all patterns must bind exactly the same set of variables with the same type.

Layered patterns

Layered patterns (also called as patterns) have been generalized to allow arbitrary patterns on both sides (in contrast to just an identifier on the left hand side as in SML). This is useful as it allows to put the identifier on either side:

fun f(xs as x::xr) = bla
fun g(x::xr as xs) = blo

Guard patterns

The most important extension are pattern guards. These allow decorating patterns with boolean conditions. A guarded pattern matches, if the pattern matches and the guard expression evaluates to true. The guard expression can refer to variables bound by the nested pattern:

fun f(x,y) if (x = y) = g x
  | f(x,y)            = h y

Guards can be nested into other patterns. Any side effect produced by the guard expression occurs whenever the pattern is tried to be matched.

Pattern syntax

atpat ::= ...
( pat1 | ... | patn ) alternative (n≥2)
pat ::= ...
pat as pat layered (R)
pat if atexp guarded (L)
exp ::= ...
exp handle <|> match handle exception
case exp of <|> match case analysis
fn <|> match function
datbind ::= tyvarseq tycon = <|> conbind <and datbind>
datdesc ::= tyvarseq tycon = <|> condesc <and datdesc>

________ Bars and semicolons _________________________________________

Optional bar

An optional bar is allowed before the first rule of a match, yielding more regular and editing-friendly notation:

case f n of
   | LESS => f(n-1)
   | EQUAL => n
   | GREATER => f(n+1)

This is also supported for fn and handle, and for function declarations using fun.

Optional bars are also allowed with datatype declarations and specifications:

datatype order =
   | LESS
   | EQUAL
   | GREATER

Optional semicolon

In a similar vein, a terminating semicolon optionally is allowed in sequence expressions:

(
    print "[";
    print(Int.toString n);
    print "]";
)

and

let
    val s = Int.toString n
in
    print "[";
    print s;
    print "]";
end

________ Recursive definitions _______________________________________

Recursive declarations

SML only allows function expressions on the right hand side of val rec. Alice is a bit more permissive. For example, one can construct cyclic lists:

val rec xs = 1::2::xs

Or regular trees:

datatype tree = LEAF | BRANCH of tree * tree

val rec tree = BRANCH(BRANCH(tree,LEAF), tree)

The right-hand sides of recursive bindings may be any expressions that are non-expansive (i.e. syntactic values) and match the corresponding left-hand side patterns (statically). Unfounded recursion is legal, but evaluates to futures that cannot be eliminated:

val rec (x,y) = (y,x)

Note that the same data structures are constructable by explicit use of promises.

Recursive expressions

Recursive values may be constructed directly without resorting to recursive declarations:

val l = rec xs => 1::2::xs

Recursive expression syntax

exp ::= ...
rec pat => exp recursion

Such rec expressions are expanded as a derived form:

rec pat => exp ==> let val rec vid as pat = exp in vid end

where vid is a new identifier.


________ Conditionals ________________________________________________

For imperative code it is sometimes convenient to have a conditional without an obligatory else branch. Alice ML provides this as a trivial derived form.

Conditional syntax

exp ::= ...
if exp1 then exp2 <else exp3> conditional

A conditional with an omitted branch is a derived form:

if exp1 then exp2 ==> if exp1 then exp2 else ()

Note that the expansion implies that exp2 must have type unit.


________ Finalization ________________________________________________

Cleaning up resources after a computation usually has to be done even if the computation exits abnormally, with an exception. To make this convenient, Alice ML has syntactic sugar for performing a finalization action after evaluating another expression, regardless of whether this other expression terminates regularly or exceptionally.

Finalization syntax

exp ::= ...
exp1 finally exp2 finalization (L)

Finalization is a derived form:

exp1 finally exp2 ==> let
    val vid2 = fn _ => exp2
    val vid1 = exp1 handle vid3 => (vid2(); raise vid3)
in
    vid2(); vid1
end

where vid1,...,vid3 are new identifiers.

Note that finally can be conveniently combined with handle, to achieve an effect similar to the try...catch...finally syntax known from other languages, e.g.:

exp0
handle p1 => exp1
     | p2 => exp2
finally exp3

________ Assertions __________________________________________________

Syntactic sugar is provided for expressing preconditions and other assertions conveniently. If an assertion fails, the exception Assert is raised, indicating the location of the failed assertion. Assertions can be activated selectively with the --assert compiler switch.

Assertion syntax

exp ::= ...
assert<d> exp boolean assertion
assert<d> exp of pat pattern assertion
assert<d> exp raise pat exception assertion
assert<d> exp do exp boolean precondition
assert<d> exp of pat do exp pattern precondition

Assertions are derived forms:

assert exp1 do exp2 ==> if exp1 then exp2 else raise Assert(_file_,_line_)
assert exp1 of pat do exp2 ==> assert (case exp1 of pat => true | _ => false) do exp2
assert exp ==> assert exp do lazy raise Assert(_file_,_line_)
assert exp of pat ==> assert exp of pat do ()
assert exp raise pat ==> assert ((exp; false) handle pat => true | _ => false) do ()

Here, _line_ will be on the same line where the assert keyword occurred.

Assertions can be assigned an explicit level by appending a digit to the assert keyword. Such assertions can be controlled via the --assert compiler switch that specifies the assertion level (0-9) of the compiled code. Any assertion that has a level annotation higher then that assertion level will be disabled. Note that level 0 assertions cannot be disabled. An unannotated assertion defaults to level 0.

When an assertion is disabled, the basic derived form is resolved differently:

assertd exp1 do exp2 ==> exp2

From this, the other expansions follow:

assertd exp1 of pat do exp2 ==> exp2
assertd exp ==> lazy raise Assert(_file_,_line_)
assertd exp of pat ==> ()
assertd exp raise pat ==> ()

Note that boolean assertions of the form assert exp are fully polymorphic. This enables the following idiom for indicating "impossible" cases:

fun f (x::xs) = g x
  | f  nil    = assert false  (* we know the list is non-empty *)

The value of such an assert expression should never be used and is thus represented by a failed future. Accessing it will also raise an Assert exception.


________ Statements __________________________________________________

The common SML idiom of writing a declaration

val _ = exp

for causing a side effect can be abbreviated to

do exp

This is a simple derived form:

do exp ==> val _ : {} = exp

I.e. the expression must have type unit.



last modified 2007/03/30 17:43