Most Alice extensions to SML'97 are conservative. There are some incompatibilies with SML'97 however. Most of them are quite pathological, caught at compile time, and can easily be fixed in an SML compatible way. This page describes known deviations and possible (backward-compatible) workarounds.
Also be aware of some limitations of the current version of Alice, which might produce additional incompatibilities.
Alice ML supports line comments, which start with the character sequence (*). Valid SML programs that contain a closing parenthesis as the first character of a regular comment (including nested ones) will hence be parsed incorrectly.
The following are reserved words in Alice and may not be used as identifiers:
any assert assertd constructor exttype fct finally from import non lazy pack spawn unpack withfun withval _file_ _line_
The latter two are not valid SML identifiers, but would be parsed as a wildcard followed by an identifier in SML.
When open is used with multiple arguments this is taken as sugar for multiple open declarations. This results in slightly different scoping rules, when nested structures are present.
structure A = struct structure B = struct val x = 1 end end structure B = struct val x = "1" end open A B val y = x
According to Standard ML, y should have type string because x refers to B.x. In Alice ML however, x refers to A.B.x and hence y is assigned type int.
Open pulls in infix status. Opening a structure that
will change the infix environment, while in SML it would not.
structure S = struct infix ++ fun l1++l2 = l1 @ l2 end open S val l = ++([1],[2]) (* error: misplaced infix *)
signature SIG = sig val ++ : 'a list * 'a list -> 'a list end structure S : SIG = struct infix ++ fun l1++l2 = l1 @ l2 end open S val l = ++([1],[2])
val l = op++([1],[2])
Recursive value bindings do not remove constructor status on the identifiers bound. You cannot bind functions to an identifier that was a constructor previously.
val rec NONE = fn x => x fun NONE x = x
Both these declarations are legal in SML'97 due to an artefact of the formal language specification and would introduce a function named NONE, hiding the constructor status of NONE. In Alice, they produces a type clash because they are interpreted as trying to match NONE with a lambda expression.
Note: This behaviour is consistent with SML'90 as well as several other SML implementations, and probably what the user would expect.
Since Alice is a concurrent language, matches containing impure patterns - particularly ref patterns - may behave slightly odd. Patterns that are exhaustive in plain SML may not necessarily be so in Alice. Also, pattern matching may trigger side-effects, when a lazy future is requested by matching and the corresponding computation contains effects.
fun f (ref false) = 1 | f (ref true) = 2
The match in this declaration will be flagged as unexhaustive by the Alice compiler. The reason is that pattern matching is not atomic, so a concurrent thread may have altered the reference between both tests - making them both fail! A particular example of a non-matching application is the following:
val rec r = ref (lazy (r := false; true)) f r
In this example, testing for the first clause will first dereference r and then request the lazy future, which determines to true, so that the clause does not match. In the meantime, the triggered computation has set r to false, hence the consecutive test, which dereferences r for a second time, will see false and fail likewise.
Be aware that patterns with ref are only ever considered exhaustive by the compiler if the argument pattern is exhaustive in itself - information from different patterns for the same reference cannot be accumulated.
Alice' structural datatypes come with some restrictions that are not present in SML. In particular, Alice demands that, whenever a datatype constructor is used in an expression, pattern, the corresponding type is visible. Dually, if a datatype is replicated, all its constructors must be visible.
The following program will not type-check:
datatype t = C type t = int val c = C
The datatype replication in the last line of the following snippet is invalid as well:
datatype t = C datatype u = C | D datatype v = datatype t
Though one does not usually write such code directly, these situations can appear implicitly through liberal use of open.
For interoperability reasons, Alice currently has the concept of syntactic arity for constructors. For example, in
type t = int * real datatype t = A of int * real | B of {a : bool, b : string} | C of t
constructor A and B both have arity 2, while C has arity 1. Usually, syntactic arity can be ignored. However, signature matching is restricted to disallow changing the syntactic arity of constructors.
Note:The same restriction is imposed by Moscow ML.
Note that syntactic arity is calculated after elimination of derived forms. Therefore C has arity 3 in the following example:
datatype t = C of u withtype u = int * int * string
The following program will not elaborate in the current version of Alice:
signature S1 = sig datatype t = C of int * real end structure M1 :> S1 = (* error: mismatch *) sig type u = int * real datatype t = C of u end
Neither will:
type u = int * real signature S2 = sig datatype t = C of u end structure M2 :> S2 = (* error: mismatch *) sig datatype t = C of int * real end
In most cases, modules can be rewritten by either expanding type synonyms or by introducing auxiliary type synonyms:
structure M1 :> S1 = sig type u = int * real datatype t = C of int * real end structure M2 :> S2 = sig type u = int * real datatype t = C of u end
Transformations can get tedious in the case of higher-order functors, however.
Alice provides higher-order functors. To integrate them smoothly into the language it was necessary to give up the separation between namespaces for structures and those for functors - both are modules. This may break programs that declare structures and functors with identical names.
functor Table() = struct (* ... *) end structure Table = Table() structure Table' = Table() (* error: Table is not a functor *)
Rename your functors or structures appropriately. It is a good idea to stick to naming conventions that denote functors with names like MkTable.
In Alice, datatypes are not generative, but are just structural types similar to records. This has an impact on the use of sharing constraints, which require flexible (i.e. abstract) type constructors.
Alice relaxes the rules for sharing constraints and allows sharing of type constructors as long as one of the following conditions holds:
This makes sharing between datatypes possible in most cases. There are exceptions, however.
signature A = sig type t end signature B = sig datatype t = C end signature S = sig structure A : A structure B : B sharing type A.t = B.t (* error: types incompatible *) end
Signature S will not elaborate because type B.t is specified after A.t and is neither abstract nor identical to A.t (which is abstract).
Signature S can be made valid by reordering structure specifications:
signature S = sig structure B : B structure A : A sharing type A.t = B.t end
Suitable reordering is not always possible, however.
In general, we consider sharing constraints an obsolete feature of SML. Use where constraints instead.
Alice extends many of the structures and signatures defined by the Standard ML Basis Library. This may change the meaning of programs that open library structures, or use library signatures.
In the following snippet, the referenced function equal will be shadowed by the open declaration:
fun equal (a : 'a -> 'b, b : 'a -> 'b) = true open Int val b = equal (op+, op-) (* error: type mismatch *)