A TypeConstructor admits equality if whenever it is applied to equality types, the result is an EqualityType. This notion enables one to determine whether a type constructor application yields an equality type solely from the application, without looking at the definition of the type constructor. It helps to ensure that PolymorphicEquality is only applied to sensible values.
The definition of admits equality depends on whether the type constructor was declared by a type definition or a datatype declaration.
Type definitions
For type definition
type ('a1, ..., 'an) t = ...
type constructor t admits equality if the right-hand side of the definition is an equality type after replacing 'a1, …, 'an by equality types (it doesn’t matter which equality types are chosen).
For a nullary type definition, this amounts to the right-hand side being an equality type. For example, after the definition
type t = bool * int
type constructor t admits equality because bool * int is an equality type. On the other hand, after the definition
type t = bool * int * real
type constructor t does not admit equality, because real is not an equality type.
For another example, after the definition
type 'a t = bool * 'a
type constructor t admits equality because bool * int is an equality type (we could have chosen any equality type other than int).
On the other hand, after the definition
type 'a t = real * 'a
type constructor t does not admit equality because real * int is not equality type.
We can check that a type constructor admits equality using an eqtype specification.
structure Ok: sig eqtype 'a t end =
struct
type 'a t = bool * 'a
end
structure Bad: sig eqtype 'a t end =
struct
type 'a t = real * int * 'a
end
On structure Bad, MLton reports the following error.
Type t admits equality in signature but not in structure. not equality: [real] * _ * _
The not equality section provides an explanation of why the type did not admit equality, highlighting the problematic component (real).
Datatype declarations
For a type constructor declared by a datatype declaration to admit equality, every variant of the datatype must admit equality. For example, the following datatype admits equality because bool and char * int are equality types.
datatype t = A of bool | B of char * int
Nullary constructors trivially admit equality, so that the following datatype admits equality.
datatype t = A | B | C
For a parameterized datatype constructor to admit equality, we consider each variant as a type definition, and require that the definition admit equality. For example, for the datatype
datatype 'a t = A of bool * 'a | B of 'a
the type definitions
type 'a tA = bool * 'a
type 'a tB = 'a
both admit equality. Thus, type constructor t admits equality.
On the other hand, the following datatype does not admit equality.
datatype 'a t = A of bool * 'a | B of real * 'a
As with type definitions, we can check using an eqtype specification.
structure Bad: sig eqtype 'a t end =
struct
datatype 'a t = A of bool * 'a | B of real * 'a
end
MLton reports the following error.
Type t admits equality in signature but not in structure. not equality: B of [real] * _
MLton indicates the problematic constructor (B), as well as the problematic component of the constructor’s argument.
Recursive datatypes
A recursive datatype like
datatype t = A | B of int * t
introduces a new problem, since in order to decide whether t admits equality, we need to know for the B variant whether t admits equality. The Definition answers this question by requiring a type constructor to admit equality if it is consistent to do so. So, in our above example, if we assume that t admits equality, then the variant B of int * t admits equality. Then, since the A variant trivially admits equality, so does the type constructor t. Thus, it was consistent to assume that t admits equality, and so, t does admit equality.
On the other hand, in the following declaration
datatype t = A | B of real * t
if we assume that t admits equality, then the B variant does not admit equality. Hence, the type constructor t does not admit equality, and our assumption was inconsistent. Hence, t does not admit equality.
The same kind of reasoning applies to mutually recursive datatypes as well. For example, the following defines both t and u to admit equality.
datatype t = A | B of u
and u = C | D of t
But the following defines neither t nor u to admit equality.
datatype t = A | B of u * real
and u = C | D of t
As always, we can check whether a type admits equality using an eqtype specification.
structure Bad: sig eqtype t eqtype u end =
struct
datatype t = A | B of u * real
and u = C | D of t
end
MLton reports the following error.
Error: z.sml 1.16. Type t admits equality in signature but not in structure. not equality: B of [u] * [real] Error: z.sml 1.16. Type u admits equality in signature but not in structure. not equality: D of [t]