Zulip Chat Archive

Stream: general

Topic: Induction principles from recursion: Beta testers wanted


Joachim Breitner (Feb 09 2024 at 10:58):

As part of the lean fro roadmap, I am working on a feature that can greatly simplify proving things about recursive functions.

If your function is structurally recursive, then normal induction on the decreasing parameter works fine. But if the function isn’t, things often get hairy: You often have to replicate the function’s case using splits, navigating to the recursive calls, and to get your induction hypothesis for the recursive calls you often have to recreate the full termination argument and proof (e.g. using termination_by) in every theorem.

With this feature you will get a an induction principle that exactly captures the structure of your recursive function, with one subgoal per branch of the function, and the induction hypthesis provided exactly for the arguments to the recursive calls. For example, given

def ackermann : Nat  Nat  Nat
  | 0, m => m + 1
  | n+1, 0 => ackermann n 1
  | n+1, m+1 => ackermann n (ackermann (n + 1) m)

you’ll get

ackermann.induct (motive : Nat  Nat  Prop) (case1 :  (m : Nat), motive 0 m)
  (case2 :  (n : Nat), motive n 1  motive (Nat.succ n) 0)
  (case3 :  (n m : Nat), motive (n + 1) m  motive n (ackermann (n + 1) m)  motive (Nat.succ n) (Nat.succ m))
  (x x : Nat) : motive x x

This also works for mutual and nested recursion, for example for

inductive Tree | node : List Tree  Tree
def Tree.rev : Tree  Tree | node ts => .node (ts.attach.map (fun t, _ht => t.rev) |>.reverse)

you’ll get

Tree.rev.induct (motive : Tree  Prop)
  (case1 :  (ts : List Tree), ( (t : Tree), t  ts  motive t)  motive (Tree.node ts)) (x : Tree) : motive x

Here is an example proof using one of these induction principles (not the best example, because induction n would work here as well, but still):

theorem ackermann_pos (n m : Nat) : 0 < ackermann n m := by
  induction n, m using ackermann.induct
  all_goals simp only [ackermann] at * -- reduce function application
  case case1 m => exact Nat.zero_lt_succ m
  case case2 n IH => exact IH
  case case3 n m _IH1 IH2 => exact IH2

A larger, realistic example, including mutual recursion and a notable reduction in size and proof complexity, can be found on the Cedar repo.

Eventually, this should work out of the box (as it already does in Isabelle and others), but before I move my prototype (currently at https://github.com/nomeata/lean-wf-induct) into lean I’d like to collect more user feedback and get more testing. So if you are using well-founded recursion in your Lean work and are eager to get your hands early on this tool (or just want to help), that would be greatly appreciated.

To use this, add

require wfinduct from git
  "https://github.com/nomeata/lean-wf-induct"

to your lakefile.lean, use the command

#derive_induction foo

after you defined your function foo, and then you can use induction x, y, z using foo.induct.

Also let me know if you have trouble getting wfinduct to work with your particular version of lean.

Kim Morrison (Feb 09 2024 at 13:44):

@Joachim Breitner if there is interest from Mathlib users it seems reasonable we could add this as a dependency there. This project has a pretty short half-life before it is absorbs into Lean, so it's not a long-term commitment.

Eric Wieser (Feb 09 2024 at 14:18):

Is it a easy generalization to produce a version where motive .. : Sort u instead of Prop? Or is that an ill-defined request?

Joachim Breitner (Feb 09 2024 at 14:31):

Not sure about easy, and I don’t know it would reduce usefully, also and not sure how hard it will be to provide equational lemmas. But besides that it’s very likely doable. Do you have a concrete application in mind?

Eric Wieser (Feb 09 2024 at 15:59):

A contrived case would be a function with a return type of Fin (ackermann m n) that I want to compute via the same recursion pattern, but I indeed don't have a concrete application right now

Eric Wieser (Feb 09 2024 at 15:59):

Perhaps a better justification is that if such a Sort-valued function existed, it could perhaps be used in the compilation of ackermann itself.


Last updated: May 02 2025 at 03:31 UTC