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 add : amount * amount -> amount. However, one would get exactly the same reassuring type if one had mistakenly defined:
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:
datatype dollars = D of int datatype cents = C of int
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).