The description-based @[derive Param] handler #
ParamDerive.lean specified the deriving-handler algorithm (its 6-step design
note) and hand-ported the representative inductive shapes (R_sum/paramSum,
R_nat/paramNat); ParamData.lean hand-ported R_prod/R_option/R_list.
This file turns the front half of that spec into a running elaborator: an
IndDesc extraction in AdapTT's style, a mixed-variance guard, and a generator
that emits the relational lift R_<T> of a simple inductive uniformly from its
constructor signatures — the constructor-wise Forall₂-shape congruence the
hand-ports reproduce by hand.
Implemented vs. specified #
Running elaborator (this file, exercised by the demos below):
IndDesc/CtorDesc/FieldKind— AdapTT'sIndDescscheme: a constructor is a list of fields, each field classified asParam.occPol— a polarity (variance) analysis of a parameter's occurrences in a field type, flipping polarity under each arrow domain.Param.toIndDesc : Name → MetaM IndDesc— reads anInductiveValviagetConstInfoInduct, walks each constructor's field telescope (forallTelescopeReducing), and classifies every field. Here the mixed-variance guard applies: a parameter that occurs in both co- and contravariant position (the canonical non-example, e.g. a field of typeA → A) is rejected with AdapTT'sNonExample (Mixed variance); a purely contravariant occurrence is likewise rejected (aParamlift ofAis a relationA → A' → Prop, which only supplies a covariant element relation).Param.mkRelInductive : IndDesc → Name → CommandElabM (TSyntax \command)— the generator: emits`inductive R_<T> {A₀ A₀' …} (P₀ : A₀ → A₀' → Prop) … : T A₀ … → T A₀' … → Propwith one constructor per source constructor, each taking the per-field relatedness hypotheses (Pᵢ aⱼ aⱼ'for a param field; the recursiveR_<T> …for a recursive field;aⱼ = aⱼ'for anotherfield) and concluding`R_<T> P… (c a…) (c a'…). This is exactly the shape ofR_sum/R_list` etc.Param.mkParamMap1Cmds : IndDesc → Name → …— for a non-recursive inductive (records / enums — the crypto carriers), the relation is upgraded to a full annotatedParam: it emits the forward mapT.paramMap(constructor-wise, applying each parameter's forward map to the matching field) and the instanceT.param_instance : Param .map3 .map0 (T A…) (T A'…)whoseRis the generatedR_<T>. Themap_in_RandR_in_mapproof fields are synthesized constructor-wise (cases+exact R_<T>.<c> …/simp [T.paramMap, …]) — a finite per-constructor term, no induction needed because no field is the inductive itself. Element inputs are taken atParam .map3 .map3(the level supplyingmap/map_in_R/R_in_map, e.g.paramId's level). This is exactly the shapeparamProd/paramOptionreproduce by hand, at the forward-map3level so the result feedscastViaParam.Param.isUniformRecursive/Param.mkParamMapRecCmds— for a uniform-recursive inductive (List/Tree-shape: every recursive field isTapplied to the same parameters), the same fullParam .map3 .map0instance is generated, but the three forward fields are recursive:T.paramMapby structural recursion (recurse on recursive fields),map_in_Rbyinductionon the value,R_in_mapbyinductionon theR_<T>derivation — recursive arms use the induction hypothesis, param arms the elementPⱼ.fwd.*.isUniformRecursivegates the path: non-uniform recursion (nesting / arrows / differing args / indices) stays relation-only.Param.mkParamDeriveCmds/mkParamInstanceHandler— registered as the@[derive Param]deriving handler (registerDerivingHandler): onderiving instance Param for T(or aderiving Paramclause) it elaborates the generatedR_<T>relation and theT.paramMap/T.param_instance(non-recursive or uniform-recursive) into the environment.
What the map-instance synthesis covers, and the boundary:
- Non-recursive inductives (records / enums): a full
Param .map3 .map0instance — forward map plus both graph inclusions — is synthesized generically (mkParamMap1Cmds, finite per-constructor terms).deriving Paramon a crypto record yields a usableParam, not justR_<T>. - Uniform-recursive inductives (List/Tree-shape — every recursive field is the
inductive applied to the same parameters): also a full
Param .map3 .map0instance (mkParamMapRecCmds). The forward mapT.paramMapis generated by structural recursion (recurse on recursive fields; the equation compiler discharges termination);map_in_Rbyinductionon the value (recursive arms use the IH, param armsPⱼ.fwd.map_in_R);R_in_mapbyinductionon theR_<T>derivation (recursive arms use the IH, param armsPⱼ.fwd.R_in_map). This reproduces the hand-writtenparamList/paramOptionofParamDatagenerically, at the fullmap3forward level. The uniform-recursion guard isisUniformRecursive. - Non-uniform recursion is out of scope (the labelled residual): a recursive
occurrence nested under another type former (
List (T A)), applied to different arguments, under an arrow (higher-order recursion), or an indexed family.isUniformRecursivereturnsfalse, so the uniform map path does not apply. (For nested recursion the relation generator itself does not apply — itsother-by-equality lift is ill-typed across the two parameter instantiations — and the registered handler declinesiv.isNestedinductives up front.)
So: the @[derive Param] attribute is registered and runs; both the non-recursive
and the uniform-recursive path generate the relation and the forward map3
Param instance with the variance guard; non-uniform recursive map/proof
synthesis is the labelled residual.
How a constructor field is lifted by the parametricity translation.
param i: a non-recursive covariant occurrence of thei-th type parameter — lifted by the supplied element relationPᵢ.recursive: the inductive applied to the params — lifted by the recursive relationR_<T>.other: a parameter-free field type — lifted by equality.
- param (idx : ℕ) : FieldKind
- recursive : FieldKind
- nestedList : FieldKind
A
List (T p…)field: the inductive nested underList, applied to the parameters in order. Lifted byList.Forall₂ (R_<T> …)in the relation,·.map (T.paramMap …)in the forward map (Extension 1, functorial-container nesting;Listis the canonical container). - other : FieldKind
Instances For
Equations
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Equations
- Transfer.Param.instDecidableEqFieldKind.decEq (Transfer.Param.FieldKind.param a) (Transfer.Param.FieldKind.param b) = if h : a = b then h ▸ isTrue ⋯ else isFalse ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq (Transfer.Param.FieldKind.param idx) Transfer.Param.FieldKind.recursive = isFalse ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq (Transfer.Param.FieldKind.param idx) Transfer.Param.FieldKind.nestedList = isFalse ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq (Transfer.Param.FieldKind.param idx) Transfer.Param.FieldKind.other = isFalse ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.recursive (Transfer.Param.FieldKind.param idx) = isFalse ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.recursive Transfer.Param.FieldKind.recursive = isTrue ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.recursive Transfer.Param.FieldKind.nestedList = isFalse Transfer.Param.instDecidableEqFieldKind.decEq._proof_8
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.recursive Transfer.Param.FieldKind.other = isFalse Transfer.Param.instDecidableEqFieldKind.decEq._proof_9
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.nestedList (Transfer.Param.FieldKind.param idx) = isFalse ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.nestedList Transfer.Param.FieldKind.recursive = isFalse Transfer.Param.instDecidableEqFieldKind.decEq._proof_11
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.nestedList Transfer.Param.FieldKind.nestedList = isTrue ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.nestedList Transfer.Param.FieldKind.other = isFalse Transfer.Param.instDecidableEqFieldKind.decEq._proof_12
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.other (Transfer.Param.FieldKind.param idx) = isFalse ⋯
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.other Transfer.Param.FieldKind.recursive = isFalse Transfer.Param.instDecidableEqFieldKind.decEq._proof_14
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.other Transfer.Param.FieldKind.nestedList = isFalse Transfer.Param.instDecidableEqFieldKind.decEq._proof_15
- Transfer.Param.instDecidableEqFieldKind.decEq Transfer.Param.FieldKind.other Transfer.Param.FieldKind.other = isTrue ⋯
Instances For
Equations
Equations
Variance analysis and the mixed-variance guard #
Polarity (variance) of a parameter p's occurrences in e.
Returns (occursPositively, occursNegatively); the domain of an arrow
flips polarity. A parameter that comes back (true, true) is mixed
variance — AdapTT's NonExample.
Is e the head selfName applied to exactly the type parameters, in order
(T p₀ … p_{k-1})? The uniform-recursion shape, reused to recognise the
List-nested occurrence.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Classify a single constructor field type. Rejects mixed- and purely
contravariant parameter occurrences (the Param element relation is
covariant). A bare covariant occurrence A becomes param i; the inductive
applied to the params becomes recursive; List (T p…) becomes nestedList;
a parameter-free type is other.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Read a simple inductive into an IndDesc, classifying every constructor
field — the MetaM extraction. The mixed-variance guard applies here.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Is T's recursion uniform (List/Tree-shape)? Every constructor field
that mentions T must be T applied to exactly the type parameters, in
order — a direct recursive sub-term. Fields with T nested under another
type former (List (T A)), applied to different arguments, or under an
arrow (higher-order recursion) make recursion non-uniform: the structural
forward map / its proofs are out of scope, so the generator keeps such
inductives relation-only.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The relation generator #
Generate the relational lift R_<T> of an inductive uniformly from its
IndDesc: a fresh inductive relation with one constructor per source
constructor, the constructor-wise Forall₂-shape congruence. This is the
shape R_sum/R_prod/R_option/R_list reproduce by hand.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The relation name the handler emits for inductive T: …T.R_param.
Equations
- Transfer.Param.relNameFor declName = declName ++ `R_param
Instances For
The forward-map name the handler emits for inductive T: …T.paramMap.
Equations
- Transfer.Param.mapNameFor declName = declName ++ `paramMap
Instances For
The Param instance name the handler emits for inductive T:
…T.param_instance.
Equations
- Transfer.Param.instNameFor declName = declName ++ `param_instance
Instances For
The Param .map3 instance generator (non-recursive inductives) #
For a non-recursive inductive T (records / enums — the crypto carriers) the
relation R_<T> generated above is upgraded to a full annotated relation: a
forward map plus both graph inclusions (map_in_R, R_in_map). All three are
finite, per-constructor terms — no induction is needed because no field is the
inductive itself — so the handler synthesizes them directly, mirroring the
hand-written paramProd/paramOption/paramList of ParamData (which build the
same constructor-wise shape) but at the map3 forward level (so the result is a
usable Param, e.g. it feeds castViaParam).
Element inputs are taken at Param .map3 .map3 (the level supplying map,
map_in_R, R_in_map — paramId's level), and the synthesized instance is
Param .map3 .map0: forward map3, backward map0 (the relation alone backward,
as for the data combinators).
Generate, for a non-recursive inductive T, the forward map
T.paramMap and the Param .map3 .map0 instance T.param_instance whose
relation is the already-generated R_<T>. The per-field proofs are finite
(no induction) because no field is the inductive itself. Recursive inductives
are routed to mkParamMapRecCmds instead; the defensive guard below returns
#[] should a recursive field reach here.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The Param .map3 instance generator (uniform-recursive inductives) #
For a uniform-recursive inductive T (List/Tree-shape: every recursive
field is T applied to the same parameters, no nesting / arrows / higher-order
recursion — isUniformRecursive), the relation R_<T> is upgraded to the same
full Param .map3 .map0 annotated relation as the non-recursive path, but the
three forward fields are recursive:
T.paramMapis generated by structural recursion: per constructor, apply each parameter'sfwd.mapto aparamfield, recurseT.paramMap …on eachrecursivefield, leaveotherfields, rebuild with the constructor. Lean's equation compiler discharges termination (direct sub-term recursion).map_in_Ris proved byinductionon the value: each arm builds theR_<T>constructor from per-field facts — aparamfield viaPⱼ.fwd.map_in_R, anotherfield viarfl, arecursivefield via the induction hypothesis.R_in_mapis proved byinductionon theR_<T>derivation (itself an inductive relation): each armsimp [T.paramMap, …]s with the per-field rewrite — aparamfield viaPⱼ.fwd.R_in_map, anotherfield via its equality hypothesis, arecursivefield via the induction hypothesis.
This reproduces the hand-written paramList/paramOption of ParamData
generically, at the full map3 forward level (so the result feeds
castViaParam). Element inputs are taken at Param .map3 .map3 (as for the
non-recursive path / paramId).
Generate, for a uniform-recursive inductive T, the structurally
recursive forward map T.paramMap and the Param .map3 .map0 instance
T.param_instance whose relation is the already-generated R_<T>. Assumes
isUniformRecursive held (otherwise returns #[], relation-only).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The Param .map3 instance generator (nested-List-recursive inductives) #
Extension 1. For an inductive T with a List (T p…) field (the Rose-shape),
the forward map recurses through List.map, and the two graph inclusions are
proved by mutual structural recursion with a List helper — the induction
tactic rejects nested inductives, but the equation compiler accepts the mutual
recursion. The relation R_<T> (from mkRelInductive) already lifts the nested
field by List.Forall₂. List is the canonical functorial container; Option /
Array are the same shape (a registered map + relation lifter + congruence).
Does T have a List (T p…) field (the nested-List-recursive shape that
mkParamMapNestedCmds handles)?
Equations
Instances For
Generate, for a nested-List-recursive inductive T, the forward map
T.paramMap (structural recursion through List.map) and the full
Param .map3 .map0 instance, with map_in_R / R_in_map proved by mutual
structural recursion + a List helper. Relation is the generated R_<T>.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Commands the @[derive Param] handler emits for one inductive: the
relational lift R_<T> (the constructor-wise congruence) and the forward map
T.paramMap + Param .map3 .map0 instance T.param_instance. The
non-recursive path (mkParamMap1Cmds), the uniform-recursive path
(mkParamMapRecCmds), and the nested-List-recursive path
(mkParamMapNestedCmds) all reach the full map3 forward instance. Other
non-uniform recursion (other-container nesting / arrows / indexed) stays
relation-only.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Elaborate a generated command at the root namespace. The generated
relation carries its fully-qualified name (<T>.R_param), so it must be
declared with the current namespace cleared — otherwise an open namespace
would be prepended a second time. (Deriving handlers proper already run at
root; this makes the generator namespace-robust for the in-file demo too.)
Equations
- One or more equations did not get rendered due to their size.
Instances For
Registration as the @[derive Param] deriving handler #
The deriving handler for Param. Generates, for each named simple inductive,
its relational lift R_<T> uniformly from the constructor signatures, with
the mixed-variance guard. Returns false (unhandled) for inductives the
uniform generator does not cover (nested / reflexive / indexed families),
so other handlers / a manual derivation can take over.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Demos #
Lean's initialize blocks take effect only after the enclosing module is
imported, so the registerDerivingHandler above is live for downstream files
(deriving Param / deriving instance Param for T there route to
mkParamInstanceHandler) but not within this module. To exercise the generator
here it is invoked directly via run_cmd — the same MetaM/command
elaboration the registered handler runs — defining the test inductives first and
then emitting their relational lift R_param.
A parameter-free enum (the base case: relation collapses to a tag match).
Instances For
- leaf {A0 A0p : Type} {P0 : A0 → A0p → Prop} {x0 : A0} {x0p : A0p} : P0 x0 x0p → R_param P0 (Tree.leaf x0) (Tree.leaf x0p)
- node {A0 A0p : Type} {P0 : A0 → A0p → Prop} {x0 x1 : Tree A0} {x0p x1p : Tree A0p} : R_param P0 x0 x0p → R_param P0 x1 x1p → R_param P0 (x0.node x1) (x0p.node x1p)
Instances For
- mk {A0 A1 A0p A1p : Type} {P0 : A0 → A0p → Prop} {P1 : A1 → A1p → Prop} {x0 : A0} {x1 : A1} {x0p : A0p} {x1p : A1p} : P0 x0 x0p → P1 x1 x1p → R_param P0 P1 (Pair.mk x0 x1) (Pair.mk x0p x1p)
Instances For
Equations
- Transfer.Param.Pair.param_instance P0 P1 = { R := Transfer.Param.Pair.R_param P0.R P1.R, fwd := { map := Transfer.Param.Pair.paramMap P0 P1, map_in_R := ⋯, R_in_map := ⋯ }, bwd := { } }
Instances For
Equations
Instances For
Equations
- Transfer.Param.Tree.paramMap P0 (Transfer.Param.Tree.leaf x0) = Transfer.Param.Tree.leaf (P0.fwd.map x0)
- Transfer.Param.Tree.paramMap P0 (x0.node x1) = (Transfer.Param.Tree.paramMap P0 x0).node (Transfer.Param.Tree.paramMap P0 x1)
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- Transfer.Param.Pair.paramMap P0 P1 (Transfer.Param.Pair.mk x0 x1) = Transfer.Param.Pair.mk (P0.fwd.map x0) (P1.fwd.map x1)
Instances For
Equations
- Transfer.Param.Tree.param_instance P0 = { R := Transfer.Param.Tree.R_param P0.R, fwd := { map := Transfer.Param.Tree.paramMap P0, map_in_R := ⋯, R_in_map := ⋯ }, bwd := { } }
Instances For
Recursive instance: Tree and MyList get the full map3 instance #
The run_cmd above ran the generator on Tree, which isUniformRecursive
accepts (the node subtrees are Tree A — the inductive at the same param), so
the uniform-recursive path applies and emits Tree.paramMap + Tree.param_instance.
A cons-shaped MyList (a param and a recursive field in one constructor)
exercises the mixed-field arm.
Equations
- Transfer.Param.MyList.param_instance P0 = { R := Transfer.Param.MyList.R_param P0.R, fwd := { map := Transfer.Param.MyList.paramMap P0, map_in_R := ⋯, R_in_map := ⋯ }, bwd := { } }
Instances For
Equations
Instances For
- nil {A0 A0p : Type} {P0 : A0 → A0p → Prop} : R_param P0 MyList.nil MyList.nil
- cons {A0 A0p : Type} {P0 : A0 → A0p → Prop} {x0 : A0} {x1 : MyList A0} {x0p : A0p} {x1p : MyList A0p} : P0 x0 x0p → R_param P0 x1 x1p → R_param P0 (MyList.cons x0 x1) (MyList.cons x0p x1p)
Instances For
Nested-List recursive instance: RoseT gets the FULL map3 instance (Extension 1) #
A Rose-shape inductive — a recursive occurrence nested under List — is routed
by isNestedListSupported to mkParamMapNestedCmds. Its forward map recurses
through List.map, and the two graph inclusions are proved by mutual structural
recursion with a List helper (the induction tactic rejects nested inductives;
the equation compiler accepts the mutual recursion).
Equations
- Transfer.Param.RoseT.paramMap P0 (Transfer.Param.RoseT.node x0 x1) = Transfer.Param.RoseT.node (P0.fwd.map x0) (List.map (Transfer.Param.RoseT.paramMap P0) x1)
Instances For
Equations
- Transfer.Param.RoseT.param_instance P0 = { R := Transfer.Param.RoseT.R_param P0.R, fwd := { map := Transfer.Param.RoseT.paramMap P0, map_in_R := ⋯, R_in_map := ⋯ }, bwd := { } }
Instances For
- node {A0 A0p : Type} {P0 : A0 → A0p → Prop} {x0 : A0} {x1 : List (RoseT A0)} {x0p : A0p} {x1p : List (RoseT A0p)} : P0 x0 x0p → List.Forall₂ (R_param P0) x1 x1p → R_param P0 (RoseT.node x0 x1) (RoseT.node x0p x1p)
Instances For
Crypto-record demos: a derived Param .map3 .map0 instance in use #
A @[derive Param] on a non-recursive crypto carrier
(an enum like a participant Role, a record like an ElGamal ciphertext or a
signature pair) yields a usable annotated relation — a forward map (via
castViaParam) and the proved graph inclusion map_in_R — not just R_<T>.
A participant role (parameter-free enum).
Instances For
- mk {A0 A0p : Type} {P0 : A0 → A0p → Prop} {x0 x1 : A0} {x0p x1p : A0p} : P0 x0 x0p → P0 x1 x1p → R_param P0 (ElGamalCt.mk x0 x1) (ElGamalCt.mk x0p x1p)
Instances For
Equations
- Transfer.Param.ElGamalCt.param_instance P0 = { R := Transfer.Param.ElGamalCt.R_param P0.R, fwd := { map := Transfer.Param.ElGamalCt.paramMap P0, map_in_R := ⋯, R_in_map := ⋯ }, bwd := { } }
Instances For
Equations
- Transfer.Param.SigPair.param_instance P0 = { R := Transfer.Param.SigPair.R_param P0.R, fwd := { map := Transfer.Param.SigPair.paramMap P0, map_in_R := ⋯, R_in_map := ⋯ }, bwd := { } }
Instances For
Equations
- Transfer.Param.SigPair.paramMap P0 (Transfer.Param.SigPair.mk x0 x1) = Transfer.Param.SigPair.mk (P0.fwd.map x0) x1
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
- mk {A0 A0p : Type} {P0 : A0 → A0p → Prop} {x0 : A0} {x1 : ℕ} {x0p : A0p} {x1p : ℕ} : P0 x0 x0p → x1 = x1p → R_param P0 (SigPair.mk x0 x1) (SigPair.mk x0p x1p)
Instances For
Equations
- Transfer.Param.ElGamalCt.paramMap P0 (Transfer.Param.ElGamalCt.mk x0 x1) = Transfer.Param.ElGamalCt.mk (P0.fwd.map x0) (P0.fwd.map x1)
Instances For
- prover : Role.prover.R_param Role.prover
- verifier : Role.verifier.R_param Role.verifier
Instances For
The mixed-variance non-example is rejected #
toIndDesc (hence the @[derive Param] handler) rejects an inductive whose
parameter occurs in both co- and contravariant position — AdapTT's
NonExample (Mixed variance).
The non-uniform-recursion boundary #
A recursive occurrence nested under another type former (List (Rose A)),
applied to different arguments, under an arrow, or in an indexed family is
non-uniform: the structural forward map / its proofs are out of scope.
isUniformRecursive is the gate — it returns false, so the uniform-recursive
map path does not apply. (For nested recursion such as Rose below the
relation generator itself does not apply either — its other-by-equality lift is
ill-typed across the two parameter instantiations, and the registered handler
declines such inductives up front via iv.isNested. The labelled residual is
thus: only the uniform List/Tree shape reaches the full map3 instance.)