hom design
Goals
Express structure-preserving maps within MoonBit’s current type system, hand the laws the type system cannot prove to the developer, and keep those obligations auditable and testable.
Constraints
- MoonBit traits only have the
Selfparameter: there are no multi-parameter traits and no associated types, so a homomorphismA -> Bcannot be a trait. - Homomorphisms out of ℕ and ℤ are unique (initial objects), which is why
FromNatandFromIntegercan live as target-side traits. Other homomorphisms are generally not unique and must be values.
Core decisions
- LCF-style certificates:
Hom[S, A, B]has private fields and is only constructed inside this package. - The only public trust entry is
Hom::postulate. Kernel rules use the package-privatetrust, so searching forpostulatelists exactly the user-level obligations. (assumeis a reserved word in MoonBit, hence the name.)Section::postulateis the trust entry for sections, and the canonicalHom::from_integerandSection::of_integralare the other leaves: their obligation sits on the trait instances. - A lift from a quotient back to its cover is a
Section, not aHom. It carries the projection as aHomand only promisesproj(lift(q)) == q; agreement with the operations on representatives follows from that law. Keeping the two apart stops a representative lift, such asInt -> BigInt, from being composed as if it were a homomorphism. - The signature is a phantom type
Son the certificate, while the algebra is passed as a dictionary valueAlgebra[S, A]: the certificate says what is preserved, the dictionary is used for checking. - Signature inclusions are
Reduct[S, T]witnesses only this package can build. - The strength of preservation is the relation
relchosen at check time, so strict, lax and approximate homomorphisms share one API.
Sections
The hom tutorial uses Section without this
mathematics.
Definition
Let π : A -> Q be a surjective homomorphism, for example the reduction
BigInt -> Int. A section is a map s : Q -> A with π(s(q)) = q for every
q: it picks one element of every class π⁻¹(q). By the first isomorphism
theorem Q is the quotient A / ker π, so a section is a choice of
representatives for a quotient algebra.
What a section preserves
For every operation ω and arguments x:
s(ω(x))andω(s(x))are congruent moduloker π. Applyπto both:π(s(ω(x))) = ω(x)by the section law, andπ(ω(s(x))) = ω(π(s(x))) = ω(x)becauseπis a homomorphism.s(ω(x)) = ω(s(x))exactly whenω(s(x))lies in the image ofs. Ifω(s(x)) = s(y), theny = π(s(y)) = π(ω(s(x))) = ω(x)by the same computation, soω(s(x)) = s(ω(x)). Converselys(ω(x))is always in the image.
The same two steps in display form, for an -ary operation and with :
so whenever has subtraction. If for some , applying gives , hence .
So Section::check only tests the section law; check_ops tests that π is
a homomorphism on lifted arguments, and point 2 follows. For Int, the image
of s is [-2^31, 2^31), and “the result is in the image” means “the result
did not wrap around”.
The carry
For addition on Int the difference in point 1 is
s(a) + s(b) - s(a + b) = c(a, b)·2^32 with c(a, b) ∈ {-1, 0, 1}, the
carry. Expanding s(a) + s(b) + s(e) in two ways gives
c(a, b) + c(a + b, e) = c(b, e) + c(a, b + e)
The identity comes from associativity. Write and , then group the sum of three lifts both ways:
Both sides are equal in ℤ, so the coefficients of agree. The bound on follows from the range of the representatives: and , so lies strictly between and , and .
So c is a 2-cocycle describing ℤ as an extension of ℤ/2^32 by 2^32ℤ. That
extension does not split, because ℤ has no element of finite order, so no
choice of representatives makes s a homomorphism. Directly: an additive
section would give
, so ,
contradicting . For multiplication the
difference is the high word of the product.
Normal forms
n = s ∘ π : A -> A is a normal form: n(n(a)) = n(a), a and n(a) are
congruent, and a, b are congruent exactly when n(a) = n(b). The
quotient operations are computed on representatives as
s(ω_Q(x)) = n(ω_A(s(x))): compute in A, then normalize. Wrapped Int
arithmetic is this computation for A = ℤ. Because Section::normalize is
built from s and π, it cannot give two congruent values different normal
forms.
Why Section is not a Hom
A section is injective and agrees with the operations on its image, so it is
easy to mistake for a homomorphism and compose it as one. Keeping it in a
separate certificate makes the difference visible in the types: Section
carries π as a Hom, and only promises π(s(q)) = q.
The law does not fix which representatives are chosen. [0, 2^32) and
[-2^31, 2^31) both give sections of BigInt -> Int; only the second
preserves the signed order of Int. Such properties need their own checks.
Alternatives rejected
- A homomorphism trait
Hom[A, B]implemented by types: it needs two type parameters, which MoonBit traits do not have, and it would allow only one homomorphism per pair of types, while a pair usually has several, such as the identity and conjugation on the complex numbers. - Plain functions
(A) -> Bwithout a certificate: nothing would separate checked homomorphisms from arbitrary conversions, and composition would not record where the obligations came from. - Treating representative lifts such as
Int -> BigIntas homomorphisms: the cocycle argument above shows that they are not, so they get their own certificate,Section. - Recording the check relation (strict, lax, tolerance) in the type: it would multiply the inference rules for each strength. The relation is chosen at check time instead, and the boundaries below state the cost.
Boundaries
- Only single-sorted signatures. Multi-sorted structures such as modules (scalars plus vectors) are out of scope for this subsystem.
- Without higher-kinded types, functorial lifts (polynomials, matrices, …) belong to their own packages and are not generalized here.
- Laws are tested, not proven. The certificate guarantees traceable origin, not that the laws hold.
- Soundness of
thenrelies on oneS-algebra per carrier. Built-in tags get this from trait coherence; dictionaries fromAlgebra::makeonly by convention. - The certificate does not record the relation used by
check_by, so lax and approximate maps compose as if they were strict. - Fixed-width integers are ℤ/2^k, not ℤ or ℕ. Maps out of them into ℤ are sections, so they agree with the operations only while arithmetic does not wrap.
- The section law does not fix which representatives are chosen, so properties such as order preservation need their own checks.