language
extensions") ?>

Besides extensions to the SML module system, Stockhausen also extends the SML core language with several features:

Other important features of Stockhausen, like lazy evaluation, futures, and threads, show up as library primitives only.

In Alice datatypes are not generative. All datatypes that have structurally equivalent definitions are compatible. For example, the following program will elaborate:

	datatype 'a t = A | B of 'a | C of 'a t
	val x = C(B 0)

	datatype 'a u = B of 'a | C of 'a u | A
	val y = B 20

	datatype 'a v = B of 'a | C of 'a t | A
	val z = A

	val l = [x,y,z]
  

This relaxation is particularly interesting for distributed programming.

Open datatypes are a generalization of SML's exception type. In effect, the programmer can arbitrarily introduce new sum types similar to exn, which have an potentially unlimited set of constructors.

An open datatype is declared as follows:

	datatype 'a message

	constructor DoThis of int : 'a message
	constructor DoThat of bool * 'a : 'a message
	constructor StopIt : 'a message
	constructor Abort = StopIt
  

Constructors can be added at any point. Like exceptions in SML, constructor declarations are dynamically generative, i.e. the following function returns a different constructor on each call:

	fun genMsg() = let constructor C : 'a message in C end
  

Note that - like exceptions - open datatypes cannot admit equality (since it is unknown whether there will be any constructors for it prohibiting that).

Exception declarations and specifications can be turned into a derived form, e.g.

        exception Error of string
	  ==>
	constructor Error of string : exn
  

Here is the complete syntax dealing with open datatypes:

dec ::= ...
constructor dconbind generative constructor
datbind ::= ...
tyvarseq tycon open datatype
dconbind ::= <op> vid <of ty> : tyvarseq longtycon <and dconbind> new constructor
<op> vid = <op> longvid <and dconbind> synonym
spec ::= ...
constructor dcondesc generative constructor
datdesc ::= ...
tyvarseq tycon open datatype
dcondesc ::= <op> vid <of ty> : tyvarseq longtycon <and dcondesc> new constructor

The language provides several useful additions to SML's patterns:

atpat ::= ...
( pat1 | ... | patn ) alternative (n>=2)
pat ::= ...
pat as pat layered (R)
non pat negated
pat where atexp guarded (L)

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 (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
  

Negated patterns are a more special feature. A negated pattern matches exactly when the operand pattern does not match. This sometimes allows specifying cases in a more natural order, in particular in combination with or patterns:

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

The nested pattern may bind variables, but these are not visible outside. (They may be useful for a local guard, for example.)

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
  

SML only allows function expressions on the right hand side of val rec. Stockhausen is a bit more permissive. Roughly, it allows any right hand side which is a syntactic value.

One can construct cyclic lists, for example:

	val xs = 1::2::xs
  

Or rational trees:

	datatype tree = LEAF | BRANCH of tree * tree

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

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
  

The syntax is obvious:

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

There are two extensions for more comfortable handling of records. First is a simple derived form for record expressions to allow punning:

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

While SML allows punning in record patterns (so that the left hand side of the example is legal), it does not allow punning in record expressions. Alice straightens this through a simple derived form (dualing the derived form for patterns):

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

The second extension is a syntax for functional record update:

exp ::= ...
{ atexp where exprow } record update

The expression atexp 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 some of the fields replaced according to exprow. 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"}