Surely you are familiar with the following points, but students may not be, and hence may be misled:

The user-defined type identifiers are purely cosmetic in SML, and therefore may give the programmer a false sense of type security. E.g. define

type dollars = int; type cents = int; type amount = dollars * cents fun add ((d1, c1) : amount, (d2, c2) : amount) = (d1+d2, c1+c2) : amountand one ML systems would report

fun add' ((d1, c1) : amount, (d2, c2) : amount) = (c1+c2, d1+d2) : amount[Incidentally, the other system you mention will report

add : ((cents * cents) * (cents * cents)) -> (cents * cents)in both cases.] One may go on to define (by mistake?) a mixture of cents and dollars

val x = 4 : dollars; val y = 99 : cents; val z1 = [x, y]; val z2 = [y, x];and one ML system says that

val z1 = [4,99] : dollars list val z2 = [99,4] : cents listwhereas the next ML system reports type cents list for both z1 and z2. The outcome is an artefact of the type checker, not the SML semantics.

Personally I have found the following two alternatives preferable:

- Either make a number of single-constructor datatypes
datatype dollars = $ of int datatype cents = C of int

- Or use a record type with appropriate labels
type amount = { dollars : int, cents : int }

Both provide better type security and consistent type reporting across ML systems (and cost nothing in terms of runtime space and time overhead).