open import 1Lab.Reflection.Signature open import 1Lab.Reflection.Subst open import 1Lab.Reflection open import 1Lab.Type.Sigma open import 1Lab.Type.Pi open import 1Lab.HLevel open import 1Lab.Path open import 1Lab.Type open import Data.Nat.Base open import Meta.Append module 1Lab.Reflection.Induction where -- H-Level instances must be in scope for the macro to work properly open import 1Lab.HLevel.Closure public open import Data.Maybe.Base using (nothing; just) public {- A macro for generating induction principles for (higher, indexed) inductive types. We support inductive types in the form presented in "Pattern Matching Without K" by Cockx et al, section 3.1, with the addition of higher inductive types. Reusing the terminology from that paper, the idea is to translate the constructors of a data type into corresponding *methods* along with spines that tell us how to apply them to build the eliminator's clauses. When generating an eliminator into (n-2)-types, we automatically omit the methods that follow from h-level (i.e. those corresponding to constructors with at least n nested `PathP`s). Due to limitations of the reflection interface, path constructors between composites are not supported. See 1Lab.Reflection.Induction.Examples for examples. -} pi-view-path : Term → Telescope pi-view-path (pi x (abs n y)) = (n , x) ∷ pi-view-path y pi-view-path (def (quote PathP) (_ h∷ lam _ (abs n y) v∷ _ v∷ _ v∷ [])) = (n , argN (quoteTerm I)) ∷ pi-view-path y pi-view-path _ = [] Replacements = List (Term × Term) subst-replacements : Subst → Replacements → Replacements subst-replacements s = map (×-map (applyS s) (applyS s)) instance Has-subst-Replacements : Has-subst Replacements Has-subst-Replacements = record { applyS = subst-replacements } record Elim-options : Type where field -- Whether to generate an induction principle or a recursion principle. -- (P : D → Type ℓ) vs (P : Type ℓ) induction : Bool -- If `just n`, assume the motive has hlevel n and omit methods accordingly. into-hlevel : Maybe Nat -- Hide the motive argument in the eliminator's type? -- (P : D → Type ℓ) vs. {P : D → Type ℓ} hide-motive : Bool -- Hide the indices in the motive's type? -- (P : (is : Ξ) → D is → Type ℓ) vs. (P : {is : Ξ} → D is → Type ℓ) hide-indices : Bool -- Hide non-inductive hypotheses in method types? -- (Psup : (x : A) (f : B x → W A B) (Pf : ∀ b → P (f b)) → P (sup x f)) -- vs. -- (Psup : {x : A} {f : B x → W A B} (Pf : ∀ b → P (f b)) → P (sup x f)) -- Arguments hidden in the constructor's type are always hidden. hide-cons-args : Bool private module Induction (opts : Elim-options) (D : Name) (pars : Nat) (ixTel : Telescope) (cs : List Name) where open Elim-options opts -- Given a term c : T, produce a replacement c↑ : T↑ (see below). -- The Replacements argument maps constructors and variables to their -- lifted version. {-# TERMINATING #-} replace : Replacements → Term → Maybe Term replace* : Replacements → Args → Args lookupR : Term → Replacements → Maybe Term lookupR t [] = nothing lookupR t@(con c _) ((con c' _ , r) ∷ rs) with c ≡? c' ... | yes _ = just r ... | no _ = lookupR t rs lookupR t@(var i _) ((var i' _ , r) ∷ rs) with i ≡? i' ... | yes _ = just r ... | no _ = lookupR t rs lookupR t (_ ∷ rs) = lookupR t rs replace rs (lam v (abs n t)) = lam v ∘ abs n <$> replace (raise 1 rs) t replace rs t@(con c args) = lookupR t rs <&> λ r → apply-tm* r (replace* rs (drop pars args)) replace rs t@(var i args) = lookupR t rs <&> λ r → apply-tm* r (replace* rs args) replace rs _ = nothing replace* rs [] = [] replace* rs (arg ai t ∷ as) with replace rs t ... | just t' = hide-if hide-cons-args (arg ai t) ∷ arg ai t' ∷ replace* rs as ... | nothing = arg ai t ∷ replace* rs as {- The main part of the algorithm: computes the method associated to a constructor. In detail, given a motive `P : (is : Ξ) → D ps is → Type ℓ` a term `c : T = Δ → D ps is` produces the "lifted" type `T↑ = Δ↑ → P is (c ρ)` where `Δ↑ ⊢ ρ : Δ` is an embedding where types of the form `S = Φ → D ps is` in Δ are replaced recursively with `{S}, S↑` in Δ↑ (this only occurs with a recursion depth of 1 due to strict positivity) and a spine α such that rec : Π P, Δ ⊢ α : Δ↑ where Π P = (is : Ξ) → (d : D ps is) → P is d or nothing if T doesn't end in `D ps`. Example: W A B sup : (a : A) (f : B a → W A B) → W A B f : B a → W A B display f = ((b : B a) → P (f b)) , (_ : Π P, b : B a ⊢ b : B a) display sup = ((a : A) {f : B a → W A B} (pf : ∀ b → P (f b)) → P (sup a f)) , (rec : Π P, a : A, f : B a → W A B ⊢ a, {f}, (λ b → rec (f b))) -} {-# TERMINATING #-} display : (depth : Nat) → (ps : Args) -- D's parameters → (P : Term) → (rs : Replacements) -- a list of replacements from terms to their lifted version, -- used for correcting path boundaries → (rec : Term) -- a variable for explicit recursion → (α : Args) -- accumulator for the spine → (c : Term) (T : Term) → Maybe (Term × Args) -- (T↑ , α) display depth ps P rs rec α c (pi (arg ai x) (abs n y)) = do let ps = raise 1 ps P = raise 1 P rs = raise 1 rs rec = raise 1 rec α = raise 1 α c = raise 1 c base = let c = c <#> arg ai (var 0 []) α = α ∷r hide-if hide-cons-args (arg ai (var 0 [])) in display depth ps P rs rec α c y <&> ×-map₁ λ y → pi (hide-if hide-cons-args (arg ai x)) (abs n y) suc depth-1 ← pure depth where _ → base just (h , α') ← pure (display depth-1 ps P rs unknown [] (var 0 []) (raise 1 x)) where _ → base let ps = raise 1 ps P = raise 1 P rs = (var 1 [] , var 0 []) ∷ raise 1 rs c = raise 1 c <#> arg ai (var 1 []) hTel = pi-view-path h l = tel→lam hTel (apply-tm* (raise (length hTel) rec) (infer-tel ixTel ∷r argN (var (length hTel) α'))) α = α ++ [ hide-if hide-cons-args (arg ai (var 0 [])) , argN l ] y = raise 1 y display depth ps P rs rec α c y <&> ×-map₁ λ y → pi (hide-if hide-cons-args (arg ai x)) (abs n (pi (argN h) (abs ("P" <> n) y))) display depth ps P rs rec α c (def (quote PathP) (_ h∷ lam v (abs n y) v∷ l v∷ r v∷ [])) = do l ← replace rs l r ← replace rs r let ps = raise 1 ps P = raise 1 P rs = raise 1 rs rec = raise 1 rec α = raise 1 α ∷r argN (var 0 []) c = raise 1 c <#> argN (var 0 []) display depth ps P rs rec α c y <&> ×-map₁ λ y → def (quote PathP) (unknown h∷ lam v (abs n y) v∷ l v∷ r v∷ []) display depth ps P rs rec α c (def D' args) = do yes _ ← pure (D ≡? D') where _ → nothing let ps' , is = split-at pars args yes _ ← pure (ps ≡? ps') where _ → nothing let Pc = apply-tm* P (if induction then hide-if hide-indices is ++ c v∷ [] else []) pure (Pc , α) display depth ps P rs rec α c _ = nothing try-hlevel : Term → TC (Maybe Term) try-hlevel T = (do maybe→alt into-hlevel m ← new-meta T unify m $ def (quote centre) $ def (quote hlevel) (unknown h∷ T h∷ lit (nat 0) v∷ []) v∷ [] pure (just m)) <|> pure nothing ×-map₁₂ : ∀ {A B C : Type} → (A → A) → (B → B) → A × B × C → A × B × C ×-map₁₂ f g (a , b , c) = (f a , g b , c) -- Loop over D's constructors and build the method telescope, constructor -- replacements and spines to apply them to. methods : Args → Term → TC (Telescope × List Args × Replacements) methods ps P = go ps P [] cs where go : Args → Term → Replacements → List Name → TC _ go ps P rs [] = pure ([] , [] , rs) go ps P rs (c ∷ cs) = do let c' = con c (hide ps) cT ← flip pi-applyTC ps =<< normalise =<< get-type c just (methodT , α) ← pure (display 1 ps P rs (var 0 []) [] c' cT) where _ → typeError [ "The type of constructor " , nameErr c , " is not supported" ] try-hlevel methodT >>= λ where (just m) → do -- The type of the method is solvable by hlevel (i.e. contractible): -- we can omit that type from the telescope and replace the method with -- a call to `hlevel 0 .centre`. let rs = (c' , m) ∷ rs go ps P rs cs <&> ×-map₁₂ id (α ∷_) nothing → do -- Otherwise, add m : T to the telescope and replace the corresponding -- constructor with m henceforth. method ← ("P" <>_) <$> render-name c let ps = raise 1 ps P = raise 1 P rs = (c' , var 0 []) ∷ raise 1 rs extend-context method (argN methodT) (go ps P rs cs) <&> ×-map₁₂ ((method , argN methodT) ∷_) (α ∷_) make-elim-with : Elim-options → Name → Name → TC ⊤ make-elim-with opts elim D = withNormalisation true do DT ← get-type D >>= normalise -- D : (ps : Γ) (is : Ξ) → Type _ data-type pars cs ← get-definition D where _ → typeError [ "not a data type: " , nameErr D ] let open Elim-options opts DTTel , _ = pi-view DT parTel , ixTel = split-at pars DTTel parTelH = hide parTel ixTel = hide-if hide-indices ixTel DTel = ixTel ∷r ("" , argN (def D (tel→args 0 DTTel))) -- (is : Ξ) (_ : D ps is) DTel = raise 1 DTel PTel = if induction then id else λ _ → [] argP = if hide-motive then argH else argN motiveTel = ("ℓ" , argH (quoteTerm Level)) -- P : DTel → Type ℓ ∷ ("P" , argP (unpi-view (PTel DTel) (agda-sort (set (var (length (PTel DTel)) []))))) ∷ [] DTel = raise 1 DTel -- We want to take is-hlevel as an argument, but we need to work in a context -- with an H-Level instance in scope. -- (d : DTel) → is-hlevel (P d) n hlevelTel = maybe→alt into-hlevel <&> λ n → "h" , argN (unpi-view (PTel DTel) (def (quote is-hlevel) (var (length (PTel DTel)) (tel→args 0 (PTel DTel)) v∷ lit (nat n) v∷ []))) -- {d : DTel} {k : Nat} → H-Level (P d) (n + k) H-LevelTel = maybe→alt into-hlevel <&> λ n → "h" , argI (unpi-view (hide (PTel DTel)) (pi (argH (quoteTerm Nat)) (abs "k" (def (quote H-Level) (var (length (PTel DTel) + 1) (tel→args 1 (PTel DTel)) v∷ def (quote _+_) (lit (nat n) v∷ var 0 [] v∷ []) v∷ []))))) ps = tel→args (length motiveTel + length hlevelTel) parTel P = var (length hlevelTel) [] module I = Induction opts D pars ixTel cs methodTel , αs , rs ← in-context (reverse (parTelH ++ motiveTel ++ H-LevelTel)) (I.methods ps P) let baseTel = parTelH ++ motiveTel ++ hlevelTel ++ methodTel DTel = raise (length hlevelTel + length methodTel) DTel P = raise (length methodTel + length DTel) P Pd = apply-tm* P (tel→args 0 (PTel DTel)) -- ∀ (ps : Γ) {ℓ} {P} (h : ∀ d → is-hlevel (P d) n)? (ms : methodTel) (d : DTel) → P d elimT = unpi-view (baseTel ++ DTel) Pd elimT' ← just <$> get-type elim <|> nothing <$ declare (argN elim) elimT for elimT' (unify elimT) -- Give a nicer error if the types don't match let ixTel = raise (length motiveTel + length hlevelTel + length methodTel) ixTel ps = raise (length methodTel + length ixTel) ps rs = raise (length ixTel) rs -- The replacements are in the H-Level context, so we substitute them back into -- our context using basic-instance. let h = length methodTel + length ixTel rs = Maybe-rec (λ n → applyS (inplaceS h (tel→lam (hide (PTel DTel)) (def (quote basic-instance) (lit (nat n) v∷ var (h + length (PTel DTel)) (tel→args 0 (PTel DTel)) v∷ []))))) id into-hlevel rs clauses ← in-context (reverse (baseTel ++ ixTel)) do let getClause = λ (c , α) → do cT ← flip pi-applyTC ps =<< normalise =<< get-type c let cTel = pi-view-path cT pats = tel→pats (length cTel) (baseTel ++ ixTel) ∷r argN (con c (tel→pats 0 cTel)) rec = def elim (tel→args (length ixTel + length cTel) baseTel) α = applyS (singletonS (length cTel) rec) α just m ← pure (I.replace rs (con c (hide ps))) where _ → typeError [] let m = apply-tm* (raise (length cTel) m) α pure (clause (baseTel ++ ixTel ++ cTel) pats m) traverse getClause (zip cs αs) define-function elim clauses default-elim = record { induction = true ; into-hlevel = nothing ; hide-motive = true ; hide-indices = true ; hide-cons-args = false } default-elim-visible = record { induction = true ; into-hlevel = nothing ; hide-motive = false ; hide-indices = false ; hide-cons-args = false } default-rec = record default-elim { induction = false } default-rec-visible = record default-elim-visible { induction = false } _into_ : Elim-options → Nat → Elim-options opts into n = record opts { into-hlevel = just n } make-elim make-rec : Name → Name → TC ⊤ make-elim = make-elim-with default-elim make-rec = make-elim-with default-rec make-elim-n make-rec-n : Nat → Name → Name → TC ⊤ make-elim-n n = make-elim-with (default-elim into n) make-rec-n n = make-elim-with (default-rec into n)