Modular.java
package com.acme.math;

import static church.lang.Array.*;
import static church.lang.Error.error;
import static church.lang.operators.Arithmetic.*;
import static church.lang.operators.Relational.*;
import static church.lang.operators.Streams.$$encode;

@SuppressWarnings("unchecked")
public class Modular<T> {
    public final T modulus;
    public final T residue;

    public Modular(T modulus, T residue) {
        this.modulus = modulus;
        this.residue = residue;
    }

    public static <T> Modular<T> modular(T modulus, T residue) {
        return new Modular<>(modulus, residue);
    }

    public static interface $Canonical<T> {
        public Modular<T> canonical(T m1, T m2, T residue);
    }

    public static <T> $Canonical<T> canonical($$neq<T> $L0, $$rem<T> $L1) {
        return (m1, m2, residue) -> $L0.$neq(m1, m2) ? error("Modular:: incompatible moduli") : modular(m1, $L1.$rem(residue, m1));
    }

    public static interface $ExtendedEuclid<T> {
        public T[] extendedEuclid(T[] u, T[] v);
    }

    public static <T> $ExtendedEuclid<T> extendedEuclid($$equal<T> $L0, $Additive_identity<T> $L1, $Quotient<T> $L2, $CreateArray<T> $L3, $$sub<T> $L4, $$prd<T, T> $L5) {
        return new $ExtendedEuclid<T>() {
            public T[] extendedEuclid(T[] u, T[] v) {
                if ($L0.$equal(v[2], $L1.additive_identity())) {
                    return u;
                }
                T q = $L2.quotient(u[2], v[2]);
                return extendedEuclid(v, $L3.createArray(3, i -> $L4.$sub(u[i], $L5.$prd(q, v[i]))));
            }
        };
    }

    public static <T, U> $$encode<T, Modular<U>> $encode($$encode<T, String> $L0, $$encode<T, U> $L1) {
        return (stream, $0) -> $L0.$encode($L1.$encode($L0.$encode($L1.$encode(stream, $0.residue), " (modulo "), $0.modulus), ")");
    }

    public static <T> $$equal<Modular<T>> $equal($$equal<T> $L0) {
        return ($0, $1) -> ($L0.$equal($0.modulus, $1.modulus) && $L0.$equal($0.residue, $1.residue));
    }

    public static <T> $$sum<Modular<T>> $sum($Canonical<T> $L0, $$sum<T> $L1) {
        return ($0, $1) -> $L0.canonical($0.modulus, $1.modulus, $L1.$sum($0.residue, $1.residue));
    }

    public static <T> $$neg<Modular<T>> $neg($$sub<T> $L0) {
        return $0 -> modular($0.modulus, $L0.$sub($0.modulus, $0.residue));
    }

    public static <T> $$prd<Modular<T>, Modular<T>> $prd($Canonical<T> $L0, $$prd<T, T> $L1) {
        return ($0, $1) -> $L0.canonical($0.modulus, $1.modulus, $L1.$prd($0.residue, $1.residue));
    }

    public static <T> $$rcp<Modular<T>> $rcp($Multiplicative_identity<T> $L0, $Additive_identity<T> $L1, $ExtendedEuclid<T> $L2, $$neq<T> $L3) {
        return $0 -> {
            T[] u  = array($L0.multiplicative_identity(), $L1.additive_identity(), $0.modulus);
            T[] v  = array($L1.additive_identity(), $L0.multiplicative_identity(), $0.residue);
            T[] lc = $L2.extendedEuclid(u, v);
            return $L3.$neq(lc[2], $L0.multiplicative_identity()) ? error("Modular: cannot invert residue as it shares a factor with modulus") : modular($0.modulus, lc[1]);
        };
    }

}