Hand-written Param lifts for sum and Nat, and the @[derive Param] design #
ParamData.lean hand-ports the relational lift for the three representative
inductive shapes Trocq's Elpi generates one file per: binary product, optional,
recursive list. This file does two things.
Mechanizes two more inductives by hand, in the exact
ParamDatastyle — the binary sumR_sum/paramSum(Coqgeneric/Param_sum.v), the representative multi-constructor non-recursive shape, and the parameter-freeNatR_nat/paramNat(Coq's natural-number relation), the representative recursive, parameter-free shape whose relation collapses to equality and whose recursor supports the fullmap3.Specifies the deriving-handler algorithm (the design note below), the procedure a
DerivingHandlerwould run to emit these files automatically — what Trocq's Elpi does in Coq.
This file contains the two hand-ported inductives (fully proved,
#print axioms-clean) and the design note. The DerivingHandler elaborator
that follows this design is ParamDeriveHandler.lean.
Design note — the @[derive Param] handler algorithm #
Given an InductiveVal for a simple inductive F with type parameters
(α₁ … α_k : Type) (no dependent indices), the handler should:
- Read the parameters. Take
kelement relationsPᵢ : Param mᵢ nᵢ αᵢ αᵢ'. The output isParam ? ? (F α₁ … α_k) (F α₁' … α_k'). - Build the relation
R_F. For each constructorcⱼ : Πfields. F α…, emit one clause relatingcⱼ x… ~ cⱼ y…by the pointwise lift of the field relations: a field of typeαᵢusesPᵢ.R; a field of recursive typeF α…usesR_F(the recursive call); a field of a previously-derived typeG β…usesR_G (…). Distinct constructors areFalse-related. (This is exactlyprodR/optionR/listR/R_sumabove.) Mechanically:inductive R_F … : F α… → F α'… → Propwith one constructor percⱼ, taking the field-relatedness hypotheses — aForall₂-style congruence. - Synthesize the forward
mapfrom the recursor.F.recwith motivefun _ => F α'…, each minor premise applyingcⱼ'to the field-images (Pᵢ.fwd.mapon element fields, the recursivemapon recursive fields). This isF.map/Sum.map/Nat.recspecialized. map_in_R(→map2a) by the recursor / induction. Induct on the source value (orsubstthe map equation); for each constructor discharge the field-relatednesses from the elementPᵢ.fwd.map_in_Rand the recursive IH. Needs eachPᵢforward-map2a.R_in_map(→map2b, hencemap3) by induction on the relation. Induct on theR_Fderivation; each constructor case rewrites by the fieldPᵢ.fwd.R_in_mapand the recursive IH. Needs eachPᵢforward-map2b. Funext-free: inductive fields are data, not function space, so unlike the arrow rule noFunextgate appears (Lean hasfunextas a theorem anyway).- Register. Emit
paramF : Param mapk … (F α…) (F α'…)and add it to the@[param]database (ParamDB.lean).
Class cap. A non-dependent inductive reaches the same level as its weakest
field input — map0–map3 univalence-free (the Map4 coherence R_in_mapK
is the only residual, as everywhere in the port). A dependent index (an
inductive family F : I → Type whose constructors constrain the index, e.g. a
length-indexed Vector) caps the derivable class: the index equality must be
transported, and recovering the round-trip coherence there is where the
map3 → map4 jump needs univalence — precisely the paper's map0–map2a
ceiling for the universe-touching cases. The handler should detect a
non-trivial index and refuse above map2a (or require the user to supply the
coherence).
Bigger picture — reuse Mathlib's Translate framework #
Mathlib refactored to_additive into a generic term-translation framework
Mathlib.Tactic.Translate (Core.lean, ~1229 LOC). It is structurally this
engine's @[param] + #transfer translator but far more mature, and this
engine's #transfer/@[param] should be re-expressed on top of it rather than
reinvented:
- Its DB is a
NameMapAttribute(Batteries.Lean.NameMapAttribute,Core.lean:16) mappingsrc ↦ TranslationInfo(:188) — exactly this engine'sparamExt : NameMap (Name × Name), but withreorder/relevantArgmetadata. TheparamExtshould become aNameMapExtensionof aParam-witness record. applyReplacementFun(:440) is a generic structural term rewriter parameterised by the DB;transformDeclRec(:742) walks a declaration and its companions. This engine'stranslateis the same walk —applyReplacementFunshould be parameterised with theParam-witness relation instead of the additive name map (i.e. carry the relatedness proof alongside the replaced constant, the one thingto_additivedoes not need).proceedFields(:983) already handles structure projections andisInductive(:1198) special-cases inductive declarations — the exact machinery the deriving-handler step (3)–(5) re-implements by hand. Reusing it gives projection/inductive translation for free.GuessName.leanauto-derives the target name from the source; this engine's translator demands an explicitc', soGuessNamewould let@[param]infer the transferred constant's name.
The reuse is: instantiate Translate's TranslateData/Config with a
Param-witness payload, replacing the additive-name substitution with the
relatedness-carrying substitution, and let its transformDeclRec/proceedFields
do the structural+inductive+projection walk that ParamTranslate does by hand.
Binary sum: the relation R_sum (Coq generic/Param_sum.v) #
The representative multi-constructor non-recursive inductive. Two Sum values
are related iff they use the same constructor with related payloads; mixed
constructors are False-related — the same shape as R_option, generalized to
two non-trivial payloads.
Trocq sumR: inl a ~ inl a' iff PA.R a a'; inr b ~ inr b' iff
PB.R b b'; mixed constructors unrelated.
Equations
- Transfer.Param.R_sum PA PB (Sum.inl a) (Sum.inl a') = PA.R a a'
- Transfer.Param.R_sum PA PB (Sum.inr b) (Sum.inr b') = PB.R b b'
- Transfer.Param.R_sum PA PB x✝¹ x✝ = False
Instances For
Trocq sum_map as a map1 record: lift each payload forward map through
the matching constructor (Sum.map). Needs PA, PB forward-map1.
Instances For
Trocq sum_map_in_R as a map2a record: the lifted map's graph is included
in R_sum, by cases on the constructor. Needs PA, PB forward-map2a.
Instances For
Trocq R_in_map_sum: R_sum-relatedness implies the lifted map sends s
to s', by cases on the constructor and the payload R_in_map. The
funext-free relation-inclusion half. Needs PA, PB forward-map2b.
paramSum: from payload relations at forward-map2a, backward-map0, the
annotated relation on Sum, forward-map1, backward-map0.
Equations
Instances For
Nat: the relation R_nat #
The representative recursive, parameter-free inductive. With no type
parameter the pointwise lift collapses to equality (zero ~ zero,
succ n ~ succ n' iff n ~ n', i.e. n = n'), and the recursor supports the
full map3 in both directions (the identity map's graph is Eq, both
inclusions identities) — univalence-free. This is the base case the handler hits
for any closed (parameter-free) inductive: the derived relation is Eq and the
Param is the diagonal at map3.
Trocq's natural-number relation: pointwise lift to Nat of the (empty list
of) parameters, i.e. plain equality. Stated by recursion to mirror the
constructor-wise shape the handler emits (zeroR/succR).
Equations
Instances For
Trocq Map3 for Nat: the identity is the forward map and its graph is
exactly R_nat (= Eq), both inclusions identities. The recursor of a
parameter-free inductive supports the full map3.
Equations
- Transfer.Param.Map3_nat = { map := id, map_in_R := Transfer.Param.Map3_nat._proof_1, R_in_map := Transfer.Param.Map3_nat._proof_2 }