60 Quotient types

Definition (Quotient Type). A construct of the form quotient T {...} defines a type named T whose values are equivalence classes over a representation defined by a set of attributes of given types. Two representatives x and y denote the same value iff the predicate in the equivalence block holds for them.


quotient Rational
{
    // Base attributes for the representation
    Integer numerator;
    Integer denominator where denominator > 0;

    // Derived attribute
    → Boolean zero = numerator = 0;

    equivalence
    {
        x ~ y where x.numerator * y.denominator = y.numerator * x.denominator;
    }
}

The ordinary attributes of a quotient type are representation fields. where clauses and constraint blocks restrict the allowed representatives. Derived attributes may be defined from the representation fields and are available through values of the quotient type.

Carrier set

The carrier of a quotient type is the set of equivalence classes of its allowed representatives.

Let S be the structural body obtained from the quotient type's representation fields, and let K be the conjunction of its representative constraints, including where clauses on representation fields and constraints written in a constraints block. The allowed representatives are:

Rep(T) = { r ∈ carrier(S) | K(r) }

If ~T is the equivalence relation declared by the equivalence block, then:

carrier(T) = Rep(T) / ~T

For the Rational example above, the representatives are pairs of integers (numerator, denominator) with denominator > 0, and two representatives are in the same carrier element exactly when cross-multiplication gives the same rational value.

Derived attributes of a quotient type may also be representation-dependent. For example, zero above is well-defined for rational values, but a derived attribute such as → hasNumeratorOne = numerator = 1 would observe the chosen representative unless the quotient also guarantees a canonical representation. The base language allows both forms.

Representatives and equality

For a quotient type T, write Rep(T) for the type of its representation. For Rational, Rep(Rational) has attributes numerator and denominator, subject to the declared representative constraints such as denominator > 0.

The equivalence block defines an equivalence relation ~T on Rep(T). A value of type T is an equivalence class of representatives under ~T. Equality of quotient values is equality of those equivalence classes, not structural equality of the representative attributes.

Representation fields are ordinary attributes for navigation. They may be used in path expressions, queries, functions, and derived attributes without a special access rule. This is useful for debugging, rendering, efficient algorithms, and definitions that are naturally written against the representation.

The equivalence predicate is checked in a context where the two distinct variables denote representatives with the declared representation fields. The predicate must have type Boolean.

Well-defined quotient operations

An expression over quotient values is well-defined on quotient values when replacing any representative by an equivalent representative leaves the result equal, using the equality of the result type.

For a function


func B f(A x);

where A is a quotient type, well-definedness means:


x ~ y ⇒ f(x) = f(y)

Here ~ is the equivalence relation of A, and = is equality of the result type B. If B is a quotient type, this result equality is its quotient equality. The same condition extends to functions with more parameters by replacing each quotient-typed argument by an equivalent representative.

Using representation fields can make an expression depend on the chosen representative. This is not rejected by the base language: a query might deliberately ask for the stored numerator and denominator of a rational value. However, an operation intended to be a property of the quotient value should be well-defined on equivalence classes. In other words, if x ~ x' and y ~ y', then such an operation should return the same result for (x, y) and (x', y'). Tools may warn about representation-dependent definitions, or treat such warnings as errors in applications that require a stricter abstraction boundary.


func Boolean ≤(Rational x, Rational y) =
    x.numerator * y.denominator ≤ y.numerator * x.denominator;

For the Rational declaration above this comparison is well-defined because every allowed representative has a positive denominator. Replacing either argument with an equivalent representative preserves the truth of the cross-multiplication comparison. If negative denominators were allowed as arbitrary representatives, this particular expression would not be well-defined without first normalising the sign or otherwise accounting for it.

The block form is deliberately heavier than a single inline equation. A quotient type has one defining equivalence relation, but that relation may need associated representation policy.

TODO: the following canonical-representative clause is an experimental idea. There is a good chance it will not be supported by the base language. This example is non-normative.


equivalence
{
    x ~ y where x.numerator * y.denominator = y.numerator * x.denominator;

    canonical x:
        gcd(abs(x.numerator), x.denominator) = 1;
}

If supported, a canonical clause would not define equality. It would select a preferred representative for each equivalence class when such a representative is available and unique. This could guide storage, rendering, indexing, and deterministic comparison, while the equivalence predicate remains the source of the quotient's equality.