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 ___________________________________

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}.

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"}

Record syntax

atexp ::= ...
{ atexp where exprow } record update
exprow ::= ...
vid <: ty> <, exprow> label as expression

The expression atexp in a record update must have a record type that includes all fields contained in exprow. The types of the fields must match. The result of evaluating a record update is a record of the same type but with the fields occuring in exprow replacing the corresponding values of the original record.

Labels as expressions are a derived form:

vid <: ty> <, exprow> ==> vid = vid <: ty> <, exprow>

________ Patterns ____________________________________________________

Alternative patterns

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

fun f(1 | 2 | 3) = 0
  | f n          = n

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) where (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 where atexp guarded (L)

________ 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.


________ 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.



last modified 2005/Aug/03 09:17