4 Type Checking
MLton's type checker follows the Definition of SML closely, so you
may find differences between MLton and other SML compilers that do
not follow the Definition so closely. In particular, SML/NJ has
many deviations from the Definition -- please see
Section A for a list of those that we are aware of.
In some respects MLton's type checker is more powerful than other
SML compilers, so there are programs that MLton accepts that are
rejected by some other SML compilers. These kinds of programs fall
into a few simple categories.
- MLton resolves flexible record patterns using a larger context than
many other SML compilers. For example, MLton accepts the
following.
fun f {x, ...} = x
val _ = f {x = 13, y = "foo"}
- MLton uses as large a context as possible to resolve the type of
variables constrained by the value resriction to be monotypes. For
example, MLton accepts the following.
structure S:
sig
val f: int -> int
end =
struct
val f = (fn x => x) (fn y => y)
end
4.1 Type error messages
To aid in the understanding of type errors, MLton's type checker
displays type errors differently than other SML compilers. In
particular, when two types are different, it is important for the
programmer to easily understand why they are different. So, MLton
displays only the differences between two types that don't match,
using underscores for the parts that do. For example, if a
function expects real * int but gets real * real, the type
error message would look like
expects: _ * [int]
but got: _ * [real]
As another aid to spotting differences, MLton places brackets [] around the parts of the types that don't match. A common
situation is when a function receives a different number of arguments
than it expects, in which case you might see an error like
expects: [int * real]
but got: [int * real * string]
The brackets make it easy to see that the problem is that the tuples
have different numbers of components -- not that the components don't
match. Contrast that with a case where a function receives the right
number of arguments, but in the wrong order.
expects: [int] * [real]
but got: [real] * [int]
Here the brackets make it easy to see that the components do not
match.
MLton's type checker is new with this release. Because of this, in
addition to differences with other SML compilers, MLton may even
differ from earlier versions of itself, when the type checker was not
so careful. If you encounter a difference between MLton and
another SML compiler, or even with an older version of MLton, and
are not able to determine if it is a bug, please send mail to
MLton@mlton.org. We would also appreciate feedback on any type error
messages that you find confusing, or suggestions you may have for
improvements to error messages.
4.2 Type information about programs
MLton has a flag, -show-basis file, that causes
MLton to pretty print to file the basis defined by the input
program. For example, if foo.sml contains
fun f x = x + 1
then mlton -show-basis foo.basis foo.sml will create foo.basis with the following contents.
val f: int -> int
If you only want to see the basis and do not wish to compile the
program, you can call MLton with -stop tc.
When MLton is called with -show-basis and no input file and,
it pretty prints the entire basis library. This can be helpful in
understanding what the basis library makes available to user programs.
When displaying signatures, MLton prefixes types defined in the
signature them with ?. to distinguish them from types defined in
the environment. For example,
signature SIG =
sig
type t
val x: t * int -> unit
end
is displayed as
signature SIG =
sig
type t = ?.t
val x: (?.t * int) -> unit
end
Notice that int occurs without the ?. prefix.
MLton also uses a canonical name for each type in the signature,
and that name is used everywhere for that type, no matter what the
input signature looked like. For example
signature SIG =
sig
type t
type u = t
val x: t
val y: u
end
is displayed as
signature SIG =
sig
type t = ?.t
type u = ?.t
val x: ?.t
val y: ?.t
end
Canonical names are always relative to the ``top'' of the signature,
even when used in nested substructures. For example,
signature S =
sig
type t
val w: t
structure U:
sig
type u
val x: t
val y: u
end
val z: U.u
end
is displayed as
signature S =
sig
type t = ?.t
val w: ?.t
val z: ?.U.u
structure U:
sig
type u = ?.U.u
val x: ?.t
val y: ?.U.u
end
end
When displaying structures, MLton uses signature contstraints
wherever possible, combined with where type clauses to specify
the meanings of the types defined within the signature.
signature SIG =
sig
type t
val x: t
end
structure S: SIG =
struct
type t = int
val x = 13
end
structure S2:> SIG = S
is displayed as
structure S: SIG
where type t = int
structure S2: SIG
where type t = S2.t
signature SIG =
sig
type t = ?.t
val x: ?.t
end