MLton

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.

Error: z.sml 1.16-1.34.
  Type in structure disagrees with signature (admits equality): t.
    structure: type 'a t = [real] * _ * _
    defn at: z.sml 3.15-3.15
    signature: [eqtype] 'a t
    spec at: z.sml 1.30-1.30

The structure: 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.

Error: z.sml 1.16-1.34.
  Type in structure disagrees with signature (admits equality): t.
    structure: datatype 'a t = B of [real] * _ | ...
    defn at: z.sml 3.19-3.19
    signature: [eqtype] 'a t
    spec at: z.sml 1.30-1.30

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-1.40.
  Type in structure disagrees with signature (admits equality): t.
    structure: datatype t = B of [_str.u] * [real] | ...
    defn at: z.sml 3.16-3.16
    signature: [eqtype] t
    spec at: z.sml 1.27-1.27
Error: z.sml 1.16-1.40.
  Type in structure disagrees with signature (admits equality): u.
    structure: datatype u = D of [_str.t] | ...
    defn at: z.sml 4.11-4.11
    signature: [eqtype] u
    spec at: z.sml 1.36-1.36