MLton

First-class polymorphism is the ability to treat polymorphic functions just like other values: pass them as arguments, store them in data structures, etc. Although Standard ML does have polymorphic functions, it does not support first-class polymorphism.

For example, the following declares and uses the polymorphic function id.

val id = fn x => x
val _ = id 13
val _ = id "foo"

If SML supported first-class polymorphism, we could write the following.

fun useId id = (id 13; id "foo")

However, this does not type check. MLton reports the following error.

Error: z.sml 1.24-1.31.
  Function applied to incorrect argument.
    expects: [int]
    but got: [string]
    in: id "foo"

The error message arises because MLton infers from id 13 that id accepts an integer argument, but that id "foo" is passing a string.

Using explicit types sheds some light on the problem.

fun useId (id: 'a -> 'a) = (id 13; id "foo")

On this, MLton reports the following errors.

Error: z.sml 1.29-1.33.
  Function applied to incorrect argument.
    expects: ['a]
    but got: [int]
    in: id 13
Error: z.sml 1.36-1.43.
  Function applied to incorrect argument.
    expects: ['a]
    but got: [string]
    in: id "foo"

The errors arise because the argument id is not polymorphic; rather, it is monomorphic, with type 'a -> 'a. It is perfectly valid to apply id to a value of type 'a, as in the following

fun useId (id: 'a -> 'a, x: 'a) = id x  (* type correct *)

So, what is the difference between the type specification on id in the following two declarations?

val id: 'a -> 'a = fn x => x
fun useId (id: 'a -> 'a) = (id 13; id "foo")

While the type specifications on id look identical, they mean different things. The difference can be made clearer by explicitly scoping the type variables.

val 'a id: 'a -> 'a = fn x => x
fun 'a useId (id: 'a -> 'a) = (id 13; id "foo")  (* type error *)

In val 'a id, the type variable scoping means that for any 'a, id has type 'a -> 'a. Hence, id can be applied to arguments of type int, real, etc. Similarly, in fun 'a useId, the scoping means that useId is a polymorphic function that for any 'a takes a function of type 'a -> 'a and does something. Thus, useId could be applied to a function of type int -> int, real -> real, etc.

One could imagine an extension of SML that allowed scoping of type variables at places other than fun or val declarations, as in the following.

fun useId (id: ('a).'a -> 'a) = (id 13; id "foo")  (* not SML *)

Such an extension would need to be thought through very carefully, as it could cause significant complications with TypeInference, possible even undecidability.