{"id":32722036,"url":"https://github.com/ionathanch/mutualinduction","last_synced_at":"2025-11-02T21:02:51.927Z","repository":{"id":281406867,"uuid":"945180993","full_name":"ionathanch/MutualInduction","owner":"ionathanch","description":"An experimental mutual induction tactic for Lean 4.","archived":false,"fork":false,"pushed_at":"2025-10-29T17:49:06.000Z","size":172,"stargazers_count":22,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-29T19:42:27.306Z","etag":null,"topics":["lean4","metaprogramming","tactics"],"latest_commit_sha":null,"homepage":"https://leanprover.zulipchat.com/#narrow/channel/239415-metaprogramming-.2F-tactics/topic/mutual.20induction.20tactic","language":"Lean","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"zlib","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ionathanch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-08T20:58:05.000Z","updated_at":"2025-10-29T17:49:10.000Z","dependencies_parsed_at":"2025-04-17T14:45:13.324Z","dependency_job_id":"9cd2940c-6ebb-4047-98ff-de829f67873e","html_url":"https://github.com/ionathanch/MutualInduction","commit_stats":null,"previous_names":["ionathanch/mutualinduction"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ionathanch/MutualInduction","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionathanch%2FMutualInduction","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionathanch%2FMutualInduction/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionathanch%2FMutualInduction/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionathanch%2FMutualInduction/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ionathanch","download_url":"https://codeload.github.com/ionathanch/MutualInduction/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionathanch%2FMutualInduction/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":282357609,"owners_count":26656057,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-02T02:00:06.609Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lean4","metaprogramming","tactics"],"created_at":"2025-11-02T21:01:28.592Z","updated_at":"2025-11-02T21:02:51.917Z","avatar_url":"https://github.com/ionathanch.png","language":"Lean","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mutual induction tactic for Lean 4\n\n* [Mutual induction with recursors](#mutual-induction-with-recursors)\n* [Mutual induction with the tactic](#mutual-induction-with-the-tactic)\n* [How does the tactic work?](#how-does-the-tactic-work)\n  1. [Compute targets and generalized variables](#1-compute-targets-and-generalized-variables)\n  2. [Check coverage and variable scoping](#2-check-coverage-and-variable-scoping)\n  3. [Generalize variables and compute motives](#3-generalize-variables-and-compute-motives)\n  4. [Apply recursors](#4-apply-recursors)\n  5. [Deduplicate subgoals](#5-deduplicate-subgoals)\n  6. [Add subgoals](#6-add-subgoals)\n* [How do joint theorems work?](#how-do-joint-theorems-work)\n* [Extensions](#extensions)\n  * [Transposed clauses](#transposed-clauses)\n  * [`with` clauses](#with-clauses)\n* [Mutual induction in other proof assistants](#mutual-induction-in-other-proof-assistants)\n  * [Rocq](#rocq)\n  * [Isabelle](#isabelle)\n\n---\n\nThis is an experimental mutual induction tactic that acts on multiple goals.\nFor now, the syntax looks like the below,\nwith no support yet for the usual `induction` tactic's features `with`\nor for generalizing different variables in each goal.\n\n```lean\nmutual_induction x₁, ..., xₙ (using r₁, ..., rₙ)? (generalizing y₁ ... yₘ)?\n```\n\nGenerating multiple goals can be done using existing declarations refinement tactics,\nbut a convenient `joint theorem` declaration form is also provided.\n\n```lean\njoint.{u,...}? ((y : A)...)?\n  theorem thm₁ x₁... : B₁\n  ...\n  theorem thmₙ xₙ... : Bₙ\nby [tactics]\n```\n\nThe doc comment for the tactic gives an example using mutual even/odd naturals,\nwhich demonstrates basic usage but not some of the subtler issues with implementation.\nHere, we'll use even/odd predicates over naturals as the running example.\n\n```lean\nmutual\ninductive Even : Nat → Prop\n  | zero : Even 0\n  | succ : ∀ n, Odd n → Even (n + 1)\ninductive Odd : Nat → Prop\n  | succ : ∀ n, Even n → Odd (n + 1)\nend\nopen Even Odd\n```\n\n## Mutual induction with recursors\n\nRecursors are generated for this mutual pair of inductives,\nwhich share the same motives and cases but with different conclusions.\n\n```lean\nEven.rec : ∀ {motive_1 : ∀ n, Even n → Prop} {motive_2 : ∀ n, Odd n → Prop},\n  -- cases for Even\n  motive_1 0 zero →\n  (∀ n (on : Odd n),  motive_2 n on → motive_1 (n + 1) (succ n on)) →\n  -- cases for Odd\n  (∀ n (en : Even n), motive_1 n en → motive_2 (n + 1) (succ n en)) →\n  -- conclusion for Even\n  ∀ {n} (en : Even n), motive_1 n en\n\nOdd.rec  : ∀ {motive_1 : ∀ n, Even n → Prop} {motive_2 : ∀ n, Odd n → Prop},\n  -- cases for Even\n  motive_1 0 zero →\n  (∀ n (on : Odd n),  motive_2 n on → motive_1 (n + 1) (succ n on)) →\n  -- cases for Odd\n  (∀ n (en : Even n), motive_1 n en → motive_2 (n + 1) (succ n en)) →\n  -- conclusion for Odd\n  ∀ {n} (on : Odd n), motive_2 n on\n```\n\nWe can conjoin the two conclusions into a single recursor using these two\nto avoid duplicating work proving the identical cases.\n\n```lean\ntheorem rec : ∀ {motive_1 motive_2}, ... →\n  (∀ {n} (en : Even n), motive_1 n en) ∧\n  (∀ {n} (on : Odd n),  motive_2 n on) := by ...\n```\n\nThe disadvantage of using the conjoined recursor\nis that the goal must have exactly this shape\nfor unification to solve goals automatically.\nOtherwise, it must be manually applied and manipulated.\nFor example, reordering and pulling the `n` out of the conjunction is equivalent:\n\n```lean\ntheorem rec' : ∀ {motive_1 motive_2}, ... → ∀ {n},\n  (∀ (on : Odd n),  motive_2 n on) ∧\n  (∀ (en : Even n), motive_1 n en) := by ...\n```\n\nbut one cannot be proven from the other without destructing a conjunction\nalong with additional instantiations or introductions.\n\n## Mutual induction with the tactic\n\nThe aim of this mutual induction tactic is to alleviate this manipulation tedium\nby avoiding conjunctions altogether.\nTo demonstrate, we prove an inversion theorem about parity of addition:\nif the addition of two naturals is even, then they are either both even or both odd;\nand if the addition of two naturals is odd, then one must be even and the other odd.\n\n```lean\njoint (n : Nat) (m : Nat)\n  theorem evenAddInv (onm : Even (n + m)) : (Even n ∧ Even m) ∨ (Odd  n ∧ Odd m)\n  theorem oddAddInv  (enm : Odd  (n + m)) : (Odd  n ∧ Even m) ∨ (Even n ∧ Odd m)\nby\n  case' evenAddInv =\u003e generalize e₂ : n + m = k₂ at onm\n  case' oddAddInv  =\u003e generalize e₁ : n + m = k₁ at enm\n```\n\nWe wish induct on the proofs of `Even (n + m)` and `Odd (n + m)`.\nJust as with the usual `induction` tactic,\nwe can't induct on inductives whose indices aren't variables,\nso we generalize `n + m` over an equality.\nThe proof state now looks like the below.\n\n```lean\n▼ case evenAddInv\nn m k₁ : Nat\ne₁ : n + m = k₁\nenm : Even k₁\n⊢ (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case oddAddInv\nn m k₂ : Nat\ne₂ : n + m = k₂\nonm : Odd k₂\n⊢ (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)\n```\n\nWe now apply mutual induction by `mutual_induction enm, onm generalizing n`,\nwhich says that we are doing induction on `enm` in goal `left` and on `onm` in goal `right`.\nIt yields the following goals.\n\n```lean\n▼ case evenAddInv.zero\nm n : Nat\ne₁ : n + m = 0\n⊢ (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case evenAddInv.succ\nm k₁ : Nat\nok : Odd k₁\nih : ∀ (n : Nat), n + m = k₁ → (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)\nn : Nat\ne₁ : n + m = k₁ + 1\n⊢ (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case oddAddInv.succ\nm k₂ : Nat\nek : Even k₂\nih : ∀ (n : Nat), n + m = k₂ → (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\nn : Nat\ne₂ : n + m = k₂ + 1\n⊢ (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)\n```\n\nThe proof proceeds by cases on `n` in the `succ` cases,\nwhich is why we generalize the induction hypothesis over it.\nThe full proof can be found at `EvenOdd.plusEvenOdd`.\n\n## How does the tactic work?\n\nThe tactic proceeds in stages:\n\n1. Compute whatever information we can from each goal independently.\n2. Ensure that the goals match the mutual inductives\n   and share the generalized variables.\n3. Compute more information from all goals in tandem.\n4. Apply the appropriate recursor for each goal.\n5. Combine duplicate subgoals from the recursors.\n6. Add subgoals to the proof state, (re)introducing variables.\n\n### 1. Compute targets and generalized variables\n\nThe user specifies that the target of `evenAddInv` is `enm` and the target of `oddAddInv` is `onm`.\nHowever, the indices of their types are considered as auxiliary targets\nbecause the motives need to abstract over them as well.\nThis would be `k₁` and `k₂` in their respective goals.\n\nWe also retrieve the inductive type to which the primary target belongs,\nalong with the positions of the motive for that inductive type and of the targets\nwithin the type of the provided recursor.\nIf no custom recursor is provided, the default recursor for that inductive type is used.\nIn our example, for the default recursors,\nthese are `Even`, `[0, 5, 6]` for the first goal,\nand `Odd`, `[1, 5, 6]` for the second.\n\nNow, we need to find the variables to generalize the goals over, which are\n\n1. The variables whose types depend on the targets; but also\n2. In the types of *those* variables, the other variables they depend on; and\n3. The other variables that the goal depends on.\n\nThe variables in bucket (1) are what get generalized in the usual `induction` tactic.\nHowever, because we are dealing with multiple goals in different contexts,\nwe need to ensure when we turn the goals into motives that they are closed,\nwhich means possibly generalizing over all variables in buckets (2) and (3).\nIn our example, this corresponds to\n\n* (1) `e₁` and `e₂` by dependency on auxiliary targets `k₁` and `k₂`, respectively;\n* (2, 3) `n` and `m` depended upon by `e₁`, `e₂`, and the goals.\n\nThis work is done by `Lean.Elab.Tactic.getSubgoal`.\n\n### 2. Check coverage and variable scoping\n\nAlthough the previous step ensures that the targets are inductive,\nwe also need to ensure that\n\n* The targets all belong to inductive types in the *same* mutual declaration;\n* The targets each belong to a *different* inductive type; and\n* ~~The targets belong to *all* of the mutual inductive types.~~\n\nThese conditions ensure that we have the correct motives needed\nto apply the recursors to each goal.\nIf a motive is missing due to a missing target for one of the inductive types,\nthen ~~we add that motive as an additional goal~~\nit gets set to the trivial constant type `PUnit`.\n\nWe then check that the provided generalized variables\nare indeed shared across goals, i.e. declared in each of the goals' contexts.\nVariables depended upon that aren't shared across goals must always be generalized\nto produce closed motives, as described in the previous step.\n\nThis work is done by `Lean.Elab.Tactic.checkTargets`\nand by `Lean.Elab.Tactic.checkFVars`.\n\n### 3. Generalize variables and compute motives\n\nAlthough we compute the variables that may be generalized independently for each goal,\nwe don't yet actually generalize them,\nbecause there may variables that happen to be common to all goals\nthat don't need to be generalized over because they'll always be in scope.\nIn this case, because `n` is explicitly generalized, `m` is the only such variable,\nwhich makes sense because it was introduced outside of the conjunction.\nOnly now do we finally generalize the variables\nand compute the motives by abstracting the goals over the targets.\n\n```lean\n▼ case evenAddInv\nm k₁ : Nat\nenm : Even k₁\nmotive_1 := λ (k₁ : Nat) (enm : Even k₁) ↦\n  ∀ (n : Nat) (e₁ : n + m = 0), (Even n ∧ Even m₁) ∨ (Odd n ∧ Odd m₁)\n⊢ ∀ (n : Nat) (e₁ : n + m = 0), (Even n ∧ Even m₁) ∨ (Odd n ∧ Odd m₁)\n\n▼ case oddAddInv\nm k₂ : Nat\nonm : Odd k₂\nmotive_2 := λ (k₂ : Nat) (onm : Odd k₂) ↦\n  ∀ (n : Nat) (e₂ : n + m = 0), (Odd n ∧ Even m₂) ∨ (Even n ∧ Odd m₂)\n⊢ ∀ (n : Nat) (e₂ : n + m = 0), (Odd n ∧ Even m₂) ∨ (Even n ∧ Odd m₂)\n```\n\nFor each goal, we know the position of the motive that applies to its target\nwithin the type of the recursor to apply,\nbut not the positions of the other motives.\nTherefore, we assume that all recursors take all motives in exactly the same order,\nwhich is true of the autogenerated recursors,\nand sort the motives in that order.\nThis means that custom recursors provided via `using`\nmust all quantify over motives in exactly the same order.\nOtherwise, finding the correct positions of all motives in all goals\nwould require O(n²) type comparisons.\n\nThis work is done by `Lean.Elab.Tactics.addMotives`.\n\n### 4. Apply recursors\n\nIn each goal, we know the primary target and its inductive type,\nso we can retrieve the recursor and instantiate it with the motives and targets,\nleaving the remaining arguments as subgoals to be solved.\n\n```lean\n▼ case evenAddInv\nm k₁ : Nat\nenm : Even k₁\nmotive_1 := λ (k₁ : Nat) (enm : Even k₁) ↦\n  ∀ (n : Nat) (e₁ : n + m = 0), (Even n ∧ Even m₁) ∨ (Odd n ∧ Odd m₁)\nmotive_2 := λ (k₂ : Nat) (onm : Odd k₂) ↦\n  ∀ (n : Nat) (e₂ : n + m = 0), (Odd n ∧ Even m₂) ∨ (Even n ∧ Odd m₂)\n⊢ @Even.rec motive_1 motive_2 ?evenAddInv.Even.zero ?evenAddInv.Even.succ ?evenAddInv.Odd.succ k₁ enm\n  : ∀ (n : Nat) (e₁ : n + m = 0), (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case oddAddInv\nm k₂ : Nat\nonm : Odd k₂\nmotive_1 := λ (k₁ : Nat) (enm : Even k₁) ↦\n  ∀ (n : Nat) (e₁ : n + m = 0), (Even n ∧ Even m₁) ∨ (Odd n ∧ Odd m₁)\nmotive_2 := λ (k₂ : Nat) (onm : Odd k₂) ↦\n  ∀ (n : Nat) (e₂ : n + m = 0), (Odd n ∧ Even m₂) ∨ (Even n ∧ Odd m₂)\n⊢ @Odd.rec motive_1 motive_2 ?oddAddInv.Even.zero ?oddAddInv.Even.succ ?oddAddInv.Odd.succ k₁ enm\n  : ∀ (n : Nat) (e₂ : n + m = 0), (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)\n```\n\nThe subgoals are collected up as a 2D array.\n\n```lean4\n[[?evenAddInv.Even.zero, ?evenAddInv.Even.succ, ?evenAddInv.Odd.succ],\n [?oddAddInv.Even.zero,  ?oddAddInv.Even.succ,  ?oddAddInv.Odd.succ]]\n```\n\nThis work is done by `Lean.Elab.Tactic.evalSubgoal`.\n\n### 5. Deduplicate subgoals\n\nBy virtue of the types of the recursors,\nthe arrays of subgoals have the same type pointwise,\nso we can pick one from each array and equate the rest to it.\nThese subgoals each correspond to one of the constructors of the mutual inductive types.\nWe intuitively expect the prefix of the name of the subgoal for a particular constructor\nto match the original goal whose target's inductive type contains that constructor.\nPicking the subgoals that prove the motive that applies to the parent goal's target\nensures that we get the correct name.\nOnce they have been picked, the inductive type's name can be removed from the subgoal's name.\n\n```lean\n▼ case evenAddInv.Even.zero\nm : Nat\n⊢ ∀ (n : Nat) (e₁ : n + m = 0), (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case evenAddInv.Even.succ\nm : Nat\n⊢ ∀ (k₁ : Nat) (enm : Even k₁),\n  (∀ (n : Nat), n + m = k₁ → (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)) →\n  ∀ (n : Nat) (e₁ : n + m = k₁ + 1), (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case oddAddInv.Odd.succ\nm : Nat\n⊢ ∀ (k₁ : Nat) (enm : Even k₁),\n  (∀ (n : Nat), n + m = k₂ → Even n ∧ Even m ∨ Odd n ∧ Odd m) →\n  ∀ (n : Nat) (e₂ : n + m = k₂ + 1), (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)\n\n▶ case oddAddInv.Even.zero := evenAddInv.Even.zero\n▶ case oddAddInv.Even.succ := evenAddInv.Even.succ\n▶ case evenAddInv.Odd.succ := oddAddInv.Odd.succ\n```\n\nThis work is done by `Lean.Elab.Tactic.deduplicate`.\n\n### 6. Add subgoals\n\nEach subgoal is added to the proof state's list of goals,\nbut only after (re)introducing into the context\nthe constructor arguments, the induction hypotheses, and the generalized variables.\nIf the goal is trivially `PUnit`, it gets solved by its constructor.\nIf an induction hypothesis is the trivial `PUnit`, it gets cleared away.\n\n```lean\n▼ case evenAddInv.zero\nm n : Nat\ne₁ : n + m = 0\n⊢ (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case evenAddInv.succ\nm k₁ : Nat\nenm : Even k₁\nih : ∀ (n : Nat), n + m = k₁ → (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)\nn : Nat\ne₁ : n + m = k₁ + 1\n⊢ (Even n ∧ Even m) ∨ (Odd n ∧ Odd m)\n\n▼ case oddAddInv.succ\nm k₁ : Nat\nenm : Even k₁\nih : ∀ (n : Nat), n + m = k₂ → Even n ∧ Even m ∨ Odd n ∧ Odd m\nn : Nat\ne₂ : n + m = k₂ + 1\n⊢ (Odd n ∧ Even m) ∨ (Even n ∧ Odd m)\n```\n\nThis work is done by `Lean.Elab.Tactic.addSubgoal`.\n\n## How do joint theorems work?\n\nThe general form of the joint theorem command is as follows:\n\n```lean\njoint.{u,...} (y : A)...\n  theorem thm₀ (x₀ : B₀)... : C₀\n  ...\n  theorem thmₙ (xₙ : Bₙ)... : Cₙ\nby [tactics]\n```\n\nThe optional universe parameters `u, ...` and variables `y, ...`\nare bound within all of the theorems' types declarations.\nAt the moment, shared universe parameters *must* be used in all theorems,\nas they will otherwise throw unused universe parameters errors.\nAs usual, the binders `x₀..., ..., xₙ...` may also be (strict) implicit `{x : B}`, `⦃x : B⦄`,\nand the type annotation `B` may be omitted.\nThe variables `y, ...` may similarly be (strict) implicit,\nbut the type annotation `A` may *not* be omitted,\nas the way the command is expanded makes inferring their types difficult.\n\nThe command first expands into an array of theorems and their proofs in proof mode.\nThis definition's name is generated from the theorems' names,\nand is currently `_thms_ ≡ _thm₀_..._thmₙ_`.\n\n```lean\ndef _thms_.{u,...} (y : A)... : Array (Σ P : Sort _, P) := by\n  refine #[\n    ⟨(∀ (x₁ : B₁)..., C₁), (λ (x₁ : B₁)... ↦ ?thm₀)⟩,\n    ...\n    ⟨(∀ (xₙ : Bₙ)..., Cₙ), (λ (xₙ : Bₙ)... ↦ ?thmₙ)⟩\n  ]\n  [tactics]\n```\n\nAt the tactics' position, there is one goal named after each theorem,\nwhose context contains the shared and bound variables,\nand whose type is the theorem's type.\n\nSubsequent theorem definitions are created for each theorem declaration,\nindexing into the array for their proofs.\n\n```lean\ndef thm₀.{u,...} (y : A)... : ∀ (x₀ : B₀)..., C₀ := (@_thms_.{u,...} y...)[0].snd\n...\ndef thmₙ.{u,...} (y : A)... : ∀ (xₙ : Bₙ)..., Cₙ := (@_thms_.{u,...} y...)[n].snd\n```\n\n## Extensions\n\nThere are many possible usability improvements for this tactic.\nA simple one is adding an option to keep missing motives as additional goals\ninstead of filling them with `PUnit`, but this use case seems rare.\nBelow are other more elaborate potential extensions.\n\n### Transposed clauses\n\nThe tactic's current syntax doesn't allow for\ngeneralizing different sets of variables in each goal,\nonly generalizing the same set in all goals.\nWe can imagine using comma-separated sets of generalized variables,\njust as the `using` clause takes comma-separated recursors.\n\n```lean\nmutual_induction x₁, ..., xₙ\n  (using r₁, ..., rₙ)?\n  (generalizing z₁..., ..., zₙ...)?\n```\n\nWhile `using` can *always* take recursors,\n`generalizing` might have goals with *no* generalized variables.\nFurthermore, generalizing the same variable in all goals\nnow requires repeating that variable in all subclauses.\nThis leads to slightly awkward notation in some situations.\n\n```lean\nmutual_induction x₁, x₂, x₃, x₄, x₅ generalizing ,,,,z\nmutual_induction x₁, x₂, x₃, x₄, x₅ generalizing z, z, z, z, z\n```\n\nAn alternate syntax would be to transpose the goals,\nspecifying the clauses for each one separately.\nWe can refer to the goals by name this way,\nwhich frees us from specifying the targets in the fixed given order.\n\n```lean\nmutual_induction\n| goal₁ =\u003e x₁ (using r₁)? (generalizing z₁...)?\n...\n| goalₙ =\u003e xₙ (using rₙ)? (generalizing zₙ...)?\n```\n\nThis is quite a bit wordier,\nbut this could be added as an alternate expanded syntax\nalongside the current terser one.\n\n### `with` clauses\n\nAt the moment, the tactic is missing support for `with` clauses.\nThe `with` clause applies tactics to the specified subgoals generated,\nand is sugar for subsequent `case` expressions.\nThe syntax for the tactic with this clause might look something like the following.\n\n```lean\nmutual_induction x₁, ..., xₙ with\n| tag₁ z₁ ... zᵢ₁ =\u003e tac₁\n...\n| tagₖ z₁ ... zᵢₖ =\u003e tacₖ\n```\n\nCombined with the transposed syntax from above, though, might be a bit unwieldy.\n\n```lean\nmutual_induction\n| goal₁ =\u003e x₁ generalizing z₂ z₃\n| goal₂ =\u003e x₂ generalizing z₁ z₃\n| goal₃ =\u003e x₃ generalizing z₁ z₂\nwith\n| goal₁.tag₁ y =\u003e tac₁\n| goal₁.tag₂ y =\u003e tac₂\n| goal₂.tag₁ y =\u003e tac₃\n| goal₃.tag₁ y =\u003e tac₄\n```\n\n## Mutual induction in other proof assistants\n\n### Rocq\n\nBy default, Rocq generates nonmutual induction principles for mutual inductives.\nFor instance, the even/odd predicates\n\n```rocq\nInductive Even : nat -\u003e Prop :=\n| zero : Even 0\n| succ_odd n : Odd n -\u003e Even (S n)\nwith Odd : nat -\u003e Prop :=\n| succ_even n : Even n -\u003e Odd (S n).\n```\n\ncome with induction principles defined via `fix` over independent motives.\n\n```rocq\nEven_ind : forall P : nat -\u003e Prop,\n  P 0 -\u003e\n  (forall n, Odd n -\u003e P (S n)) -\u003e\n  forall n, Even n -\u003e P n\n\nOdd_ind  : forall P : nat -\u003e Prop,\n  (forall n, Even n -\u003e P (S n)) -\u003e\n  forall n, Odd -\u003e P n\n```\n\nUsing `Scheme` generates the mutual induction principles we expect.\n\n```rocq\nScheme Even_Odd_ind := Induction for Even Sort Prop\n  with Odd_Even_ind := Induction for Odd  Sort Prop.\n```\n\n```rocq\nEven_Odd_ind : forall (P : forall n, Even n -\u003e Prop) (P0 : forall n, Odd n -\u003e Prop)\n  P 0 -\u003e\n  (forall n (o : Odd n), P0 n 0 -\u003e P (S n) (succ_odd n o)) -\u003e\n  (forall n (e : Odd n), P n e -\u003e P0 (S n) (odd_succ n e)) -\u003e\n  forall n (e : Even n), P n e\n\nOdd_Even_ind : forall (P : forall n, Even n -\u003e Prop) (P0 : forall n, Odd n -\u003e Prop)\n  P 0 -\u003e\n  (forall n (o : Odd n), P0 n 0 -\u003e P (S n) (succ_odd n o)) -\u003e\n  (forall n (e : Odd n), P n e -\u003e P0 (S n) (odd_succ n e)) -\u003e\n  forall n (o : Odd n), P0 n o\n```\n\nThese can be used with the induction tactic just as in Lean:\n`induction e using Even_Odd_ind with (P0 := Q)`\nin Rocq corresponds roughly to\n`induction e using Even.rec (motive_2 := Q)`\nin Lean.\n\nBut we can do one better: `Combined Scheme` conjoins the conclusions\nand deduplicates the cases, similar to what `mutual_induction` does internally.\n\n```rocq\nCombined Scheme Even_Odd_mutind from Even_Odd_ind, Odd_Even_ind.\n```\n\n```rocq\nEven_Odd_mutind : forall (P : forall n, Even n -\u003e Prop) (P0 : forall n, Odd n -\u003e Prop)\n  P 0 -\u003e\n  (forall n (o : Odd n), P0 n 0 -\u003e P (S n) (succ_odd n o)) -\u003e\n  (forall n (e : Odd n), P n e -\u003e P0 (S n) (odd_succ n e)) -\u003e\n  (forall n (e : Even n), P n e) /\\ (forall n (o : Odd n), P0 n o)\n```\n\nThis combined scheme, although conveniently generated for us,\nhas the same disadvantages as outlined in the beginning.\n\nAdditionally, Rocq provides top-level mutual theorem statements,\nwhich generates two goals where both theorems are in scope.\n\n```rocq\nLemma plusEven {n m} (e : Even (n + m)) : (Even n /\\ Even m) \\/ (Odd  n /\\ Odd m)\n with plusOdd  {n m} (o : Odd (n + m))  : (Odd  n /\\ Even m) \\/ (Even n /\\ Odd m)).\n```\n\n```rocq\nplusEven : forall n m, Even (n + m) -\u003e (Even n /\\ Even m) \\/ (Odd  n /\\ Odd m)\nplusOdd  : forall n m, Odd  (n + m) -\u003e (Odd  n /\\ Even m) \\/ (Even n /\\ Odd m))\nn, m : nat\n------------------------------------------- (1/2)\n(Even n /\\ Even m) \\/ (Odd  n /\\ Odd m)\n------------------------------------------- (2/2)\n(Odd  n /\\ Even m) \\/ (Even n /\\ Odd m))\n```\n\nThe idea with this setup is to be able to prove these lemmas by mutual recursion,\nexplicitly calling the other lemma on mutual subarguments.\nThe danger here is the same as proofs by mutual recursion in Lean:\nthe user has to make sure to only instantiate mutual lemmas on structurally smaller arguments,\nor else specify a different termination metric altogether.\n\n### Isabelle\n\n\u003csmall\u003e*I am not an Isabelle user and haven't tested any code,\n  so this information may be inaccurate.*\u003c/small\u003e\n\nThe Isar proof language for Isabelle provides an induction tactic\nthat appears to work adequately on mutual inductives as well.\nConsidering again the even/odd predicates,\nthe inversion lemma on parity of addition is stated mutually.\n\n```isabelle\ninductive even and odd where\n  \"even 0\"\n| \"even n ⟹ odd  (n + 1)\"\n| \"odd  n ⟹ even (n + 1)\"\n\nlemma\n  fixes n m :: 'a\n  shows\n    \"even (n + m) ⟹ (even n ∧ even m) ∨ (odd  n ∧ odd m)\" and\n    \"odd  (n + m) ⟹ (odd  n ∧ even m) ∨ (even m ∧ odd m)\"\n```\n\nThe proof proceeds by applying induction, which has the general form\n(assuming two goals, but more can be conjoined by `and`):\n\n```isabelle\ninduct (x = t)... and (y = u)... arbitrary: v... and w... rule: R\n```\n\ncorresponding roughly to first `generalize t = x; ...` and `generalize u = y; ...`\non the antecedents of the first and second goals respectively,\nthen induction on those antecedents generalizing `v...` and `w...` respectively,\nusing the induction principle `R`.\nEven if generalization is not needed,\nthe indices of each inductive at the very least need to all be provided.\nSo to continue our proof above,\n\n```isabelle\napply (\n  induct (k = n + m) and (k = n + m)\n  arbitrary: n and n\n  rule: even_odd.inducts\n)\n```\n\nshould advance the proof to the three cases for each constructor.\nThe advantage of this `and` syntax, in particular for `arbitrary`,\nis that different goals can have different arguments generalized;\nthe disadvantage is that if all goals generalize the same argument,\nthey still must be specified for each goal.\nAdditionally, if only the last of four goals needs an argument generalized,\nit would require writing `arbitrary: and and and n`.\n\nReference: [The Isabelle/Isar Reference Manual](https://isabelle.in.tum.de/doc/isar-ref.pdf#subsection.6.5.2)\n\n# Towards nested induction: `mk_all`\n\nAs an extra bonus, this repo also includes a `mk_all` attribute for inductive types\nthat generates new definitions that lift predicates over the given parameters\nto predicates over that inductive type.\nFor example, consider the following list type that we want to lift predicates over.\n\n```lean\n@[mk_all α]\ninductive List (α : Type) where\n  | nil : List α\n  | cons : α → List α → List α\n```\n\nBy default, `List` is in a `Type` universe.\nThen two definitions are generated: `List.all` and `List.iall`,\nwhich lift predicates over `α` to predicates over `List α`\ninto `Prop` and into `Type`, respectively.\nInternally, the predicates are implemented using the list recursor,\nbut are equivalent to the following recursive functions.\n\n```lean\ndef List.all {α : Type} (P : α → Type) : List α → Type\n  | nil =\u003e Unit\n  | cons x xs =\u003e P x × List.all P xs\n\ndef List.iall {α : Type} (P : α → Prop) : List α → Prop\n  | nil =\u003e True\n  | cons x xs =\u003e P x ∧ List.iall P xs\n```\n\nIf `List` is in the `Prop` universe,\nthen only a `List.all` into `Prop` is generated as an inductive type.\n\n```lean\ninductive List.all {α : Type} (P : α → Prop) : Lst α → Prop where\n  | nil : all P nil\n  | cons x xs : P x → all P xs → all P (cons x xs)\n```\n\nGiven some list `xs = [a, b, c, ...]`,\nthe predicate `List.all P xs` represents a list of propositions\n`[P a, P b, P c, ...]`.\nThis is particularly useful if we have an inductive nested inside of lists,\nand we want to lift a predicate over the nested inductive to a predicate on lists of them.\nThe classic example is the rose tree.\n\n```lean\ninductive Tree (α : Type) where\n  | leaf : Tree α\n  | node : α → List (Tree α) → Tree α\n```\n\nThe recursor for `Tree` treats the nesting opaquely,\ndemanding a predicate over `List (Tree α)` as a second motive,\nand generating additional cases for the nesting inductive `List`.\n\n```lean\nTree.rec : ∀ {α : Type} {motive_1 : Tree α → Type} {motive_2 : List (Tree α) → Type},\n  -- cases for Tree\n  motive_1 leaf →\n  (∀ x xs, motive_2 xs → motive_1 (node x xs)) →\n  -- cases for List\n  motive_2 nil →\n  (∀ x xs, motive_1 x → motive_2 xs → motive_2 (cons x xs)) →\n  -- conclusion for Tree\n  ∀ (t : Tree α), motive_1 t\n```\n\nWhat would be more useful is if `motive_1` were applied to each subtree in the node,\nwhich is exactly what `List.all motive_1` does.\nSuch elimination principles into `Prop` and `Type` can easily be proven.\n\n```lean\ntheorem Tree.ielim {α} (P : Tree α → Prop)\n  (hleaf : P leaf)\n  (hnode : ∀ {x xs}, List.iall P xs → P (node x xs)) :\n  ∀ t, P t := by\n  intro t\n  induction t using Tree.rec (motive_2 := List.iall P)\n  case leaf =\u003e exact hleaf\n  case node ih =\u003e exact hnode ih\n  case nil =\u003e exact ⟨⟩\n  case cons hx hxs =\u003e exact ⟨hx, hxs⟩\n\nnoncomputable def Tree.elim {α} (P : Tree α → Type)\n  (hleaf : P leaf)\n  (hnode : ∀ {x xs}, List.all P xs → P (node x xs)) :\n  ∀ t, P t := by\n  intro t\n  induction t using Tree.rec (motive_2 := List.all P)\n  case leaf =\u003e exact hleaf\n  case node ih =\u003e exact hnode ih\n  case nil =\u003e exact ⟨⟩\n  case cons hx hxs =\u003e exact ⟨hx, hxs⟩\n```\n\nUnfortunately, the recursors for nested inductives in Lean are currently noncomputable,\nso we can at most define a better noncomputable elimination principle for `Tree`.\n\nIn general, the `mk_all` attribute can be applied with multiple parameters,\nso long as each parameter appears strictly positively in the inductive definition.\nLean doesn't support nonuniform parameters,\nso generating predicate liftings for deeply nested inductives\n(such as perfect trees) is not supported either.\nFuture work may include the following:\n\n* For inductives not in `Prop`,\n  making `simp` reduce `.(i)all` on constructors\n  as if they were defined as the equivalent recursive definition.\n* Generating `.(i)all` for already-defined inductive types from other modules.\n* Generating the definitions of the `.(i)all`-augmented eliminators automatically.\n* Generating corresponding `.below` and `.brecOn` definitions\n  that incorporate `.(i)all`.\n\nThe design of `Lean.Meta.MkAll.mkAllDef` and `Lean.Meta.MkAll.mkIAllDef`\nare based on `Lean.Meta.BRecOn.mkBelowOrIBelow` and `Lean.Meta.IndPredBelow.mkBelow` respectively,\nso the proof search for the eliminators will likely follow the design of\n`Lean.Meta.BRecOn.mkBRecOn` and `Lean.Meta.BRecOn.mkBInductionOn`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionathanch%2Fmutualinduction","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fionathanch%2Fmutualinduction","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionathanch%2Fmutualinduction/lists"}