You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement multiplicity subtyping (m0 <: mw minimally; full QTT semiring partial order more generally) as a proper structural addition to the type system. The fix lands as a separate subtype? predicate composing with — not extending — unify's structural-equality semantics.
Motivated by PR #17 (eigentrust pitfall #2 — kind-level mult mismatch when a type constructor with kind Pi(m0, Type, Type) flows into a polymorphic combinator's Pi(mw, Type, Type) slot). Verified the bug reproduces on current main (post-S2.e):
Kumavis proposed a surface fix in #17 (extend classify-mult-problem with {m0, mw} lenience). We're declining that approach in favor of the structural fix tracked here, to avoid scaffolding that risks sticking around even after the structural correction lands.
The bug, structurally
map's spec elaborates {C : Type → Type} to Pi(mw, Type, Type) (the inner → defaults to mw via surf-arrow). List is registered with kind Pi(m0, Type, Type) (erased type arg). Implicit type-arg unification tries to unify these kinds, fails on the mult mismatch (m0 ≠ mw structurally), surfaces as "Multiplicity violation" at the value level — confusingly.
Under QTT's multiplicity semiring, m0 ≤ mw (zero uses trivially satisfies "any number of uses"). The relationship is a real subtype, not just a coincidence.
Soundness analysis
QTT multiplicities form a semiring with partial order:
Relation
Status
m0 ≤ mw
✅ Sound — value used 0 times satisfies "any number ≥ 0"
m1 ≤ mw
⚠️ Sound but lossy — preserves usage count but loses linearity contract; only sound at call sites not definition sites
m0 vs m1
❌ Incomparable — zero is not "exactly one" usage
For the eigentrust case: type-level kind subtyping (Pi(m0, Type, Type) <: Pi(mw, Type, Type)) is the immediate need. Value-level mult subtyping is a generalization to consider after the kind-level case lands.
Proposed shape — separate subtype? predicate
Keep unify pure (structural equality); add subtype? as the semantic-relation extension:
;; subtype? : ctx × Expr × Expr → Result;; #t — t1 is a subtype of t2 (proceed);; #f — t1 is not a subtype of t2 (caller decides — fail or fallback);; pending — depends on a meta; needs solving, return constraint
Where called:
check's Pi case (when the function's mult doesn't match the expected mult)
infer's App case (when the argument's mult doesn't match the parameter's mult)
Implicit type-arg synthesis (kind-level mult subtyping for HKT slots)
Composition with unify: unify remains structural; subtype? is checked at sites where the type system semantically permits a refinement. If unify would fail on a mult mismatch, the caller falls back to subtype?. If subtype? succeeds, the unification proceeds with the appropriate side's value (covariant: take the supertype; contravariant: take the subtype).
Mult-meta interaction: subtype? returns pending when one side is a meta. Caller then decides whether to solve the meta to make the relation hold (e.g., solving an unconstrained meta to mw to admit a value with mult m0 <: ?meta).
Scope
In scope (this issue):
Multiplicity subtyping subtype-mult? : Mult × Mult → Result
Pi-type subtyping with mult-subtyping (contravariant domain, covariant result)
Will refine with prior-art audit (a follow-up task on this issue):
subtype? predicate scaffolding: ~50 LoC
Multiplicity subtyping: ~30 LoC (small partial-order check)
Pi-type subtyping: ~50 LoC
Integration at typing rule sites: 5-10 sites, ~50-100 LoC
Mult-meta interaction: ~50-100 LoC (the tricky part)
Tests: ~100-200 LoC
Documentation: ~50 LoC
Total: 200-450 LoC, multi-day implementation. Real work but tractable.
Open question: do we have prior subtyping infrastructure (numeric subtyping is complete; capability subtyping mentioned in design docs) we can compose with? Audit pending.
Why structural rather than tactical
Per workflow.md Adversarial Framing rule: "preserved for backward-compat" / "for safety" / etc. red-flag patterns. Lenience-under-unify is a tactical fix that risks sticking around as scaffolding even after a proper subtyping framework lands. Our charter explicitly retires such patterns.
The pragmatic case for tactical fix is real (~12 LoC vs ~300 LoC; eigentrust unblocked sooner). We're choosing the structural path because:
The pattern recurs — every future "kind-level subtyping" or "value-level subtyping" question hits the same architectural seam
Prior subtyping infrastructure (numeric refinements, etc.) suggests we have machinery to compose with
PPN 4C's broader migration toward principled lattice-and-subtype reasoning makes this a natural component to land
References
Motivating PR: #17 — declined in favor of this issue
Detailed implementation design — this issue tracks the conceptual fix; design doc (docs/tracking/YYYY-MM-DD_MULT_SUBTYPING_DESIGN.md) when the work is scheduled.
Priority
Medium. Eigentrust use case is blocked without this; the architecture clarification it forces is independently valuable; not urgent enough to disrupt PPN 4C work in flight.
Summary
Implement multiplicity subtyping (
m0 <: mwminimally; full QTT semiring partial order more generally) as a proper structural addition to the type system. The fix lands as a separatesubtype?predicate composing with — not extending —unify's structural-equality semantics.Motivated by PR #17 (eigentrust pitfall #2 — kind-level mult mismatch when a type constructor with kind
Pi(m0, Type, Type)flows into a polymorphic combinator'sPi(mw, Type, Type)slot). Verified the bug reproduces on currentmain(post-S2.e):Kumavis proposed a surface fix in #17 (extend
classify-mult-problemwith{m0, mw}lenience). We're declining that approach in favor of the structural fix tracked here, to avoid scaffolding that risks sticking around even after the structural correction lands.The bug, structurally
map's spec elaborates{C : Type → Type}toPi(mw, Type, Type)(the inner→defaults to mw viasurf-arrow).Listis registered with kindPi(m0, Type, Type)(erased type arg). Implicit type-arg unification tries to unify these kinds, fails on the mult mismatch (m0 ≠ mwstructurally), surfaces as "Multiplicity violation" at the value level — confusingly.Under QTT's multiplicity semiring,
m0 ≤ mw(zero uses trivially satisfies "any number of uses"). The relationship is a real subtype, not just a coincidence.Soundness analysis
QTT multiplicities form a semiring with partial order:
m0 ≤ mwm1 ≤ mwm0vsm1For the eigentrust case: type-level kind subtyping (
Pi(m0, Type, Type) <: Pi(mw, Type, Type)) is the immediate need. Value-level mult subtyping is a generalization to consider after the kind-level case lands.Proposed shape — separate
subtype?predicateKeep
unifypure (structural equality); addsubtype?as the semantic-relation extension:Where called:
check's Pi case (when the function's mult doesn't match the expected mult)infer's App case (when the argument's mult doesn't match the parameter's mult)Composition with unify:
unifyremains structural;subtype?is checked at sites where the type system semantically permits a refinement. Ifunifywould fail on a mult mismatch, the caller falls back tosubtype?. Ifsubtype?succeeds, the unification proceeds with the appropriate side's value (covariant: take the supertype; contravariant: take the subtype).Mult-meta interaction:
subtype?returnspendingwhen one side is a meta. Caller then decides whether to solve the meta to make the relation hold (e.g., solving an unconstrained meta to mw to admit a value with multm0 <: ?meta).Scope
In scope (this issue):
subtype-mult? : Mult × Mult → ResultOut of scope (future generalizations):
2026-02-27_REFINED_NUMERIC_SUBTYPING.md)Implementation estimate (rough)
Will refine with prior-art audit (a follow-up task on this issue):
subtype?predicate scaffolding: ~50 LoCTotal: 200-450 LoC, multi-day implementation. Real work but tractable.
Open question: do we have prior subtyping infrastructure (numeric subtyping is complete; capability subtyping mentioned in design docs) we can compose with? Audit pending.
Why structural rather than tactical
Per
workflow.mdAdversarial Framing rule: "preserved for backward-compat" / "for safety" / etc. red-flag patterns. Lenience-under-unify is a tactical fix that risks sticking around as scaffolding even after a proper subtyping framework lands. Our charter explicitly retires such patterns.The pragmatic case for tactical fix is real (~12 LoC vs ~300 LoC; eigentrust unblocked sooner). We're choosing the structural path because:
References
docs/tracking/2026-04-23_eigentrust_pitfalls.md(in PR Add EigenTrust reputation algorithm: implementation, tests, benchmark #2)docs/tracking/2026-02-27_REFINED_NUMERIC_SUBTYPING.md(status COMPLETE per MEMORY.md)qtt.rkt,unify.rkt::classify-mult-problemOut of scope for this issue (explicit deferrals)
process-commandsequential orchestrator #22) but the proper fix here is downstream of PPN 4C's typing-on-network completion. Coordinate timing.docs/tracking/YYYY-MM-DD_MULT_SUBTYPING_DESIGN.md) when the work is scheduled.Priority
Medium. Eigentrust use case is blocked without this; the architecture clarification it forces is independently valuable; not urgent enough to disrupt PPN 4C work in flight.