Zulip Chat Archive

Stream: new members

Topic: induction for mutually inductive types


Malvin Gattinger (Oct 04 2023 at 19:44):

I get 'induction' tactic does not support mutually inductive types, the eliminator 'MyType.rec' has multiple motives. If needed I can try to make an mwe, but maybe someone has a hint how to deal with this in general? Is induction in Lean 4 less powerful than in Lean 3? I saw this discussion from 2021 but I am such a user that does not know what "motives" are. Do I need to understand it if I want to work with mutually inductive types in Lean 4?

Kevin Buzzard (Oct 04 2023 at 19:48):

the motive is just the thing you're trying to prove.

Malvin Gattinger (Oct 04 2023 at 19:59):

Ah, then I guess I do not really know what the eliminator of my type is. I guess to do induction on a mutually inductive type I have to show two motives, one for each of the types?

Malvin Gattinger (Oct 04 2023 at 20:03):

I'll see if I can make an MWE tomorrow. thanks for now!

Shreyas Srinivas (Oct 04 2023 at 20:07):

(deleted)

Shreyas Srinivas (Oct 04 2023 at 20:11):

(deleted)

Shreyas Srinivas (Oct 04 2023 at 20:12):

(deleted)

Malvin Gattinger (Oct 05 2023 at 16:46):

Here comes my example. I actually have three questions, and the second to me feels like a bug :thinking:

-- MWE about Mutually Inductive Types
-- A bag is a thing that contains an object.
-- A box is an object that contains two things.

import Mathlib.Data.Finset.Basic -- comment this and sizeOfThing no longer errors O.o

-- Question 1: How do I get DecidableEq for these types?
mutual
  inductive Thing : Type
    | Pizza : Thing
    | round : Thing  Thing
    | Bag : Object  Thing
    deriving Repr -- , DecidableEq -- error "default handlers have not been implemented yet"
  inductive Object : Type
    | Bike : Object
    | Box : Thing -> Thing -> Object
    deriving Repr -- , DecidableEq
end
-- if deriving is not possible, how would I write these manually?
mutual
  instance decEqThing : DecidableEq Thing := sorry
  instance decEqObject : DecidableEq Object := sorry
end


-- Question 2: When I "import Mathlib.Data.Finset.Basic" above, then this here
-- fails with: "structural recursion does not handle mutually recursive functions".

open Thing Object

mutual
  def sizeOfThing : Thing  Nat
    | Pizza => 13
    | round s => sizeOfThing s
    | Bag o => 1 + sizeOfObject o
  def sizeOfObject : Object  Nat
    | Bike => 42
    | Box t1 t2 => 10 + sizeOfThing t1 + sizeOfThing t2
end
-- decreasing_by sorry -- would this help?
-- Should I be doing it, or just avoiding the (unrelated) import above?



-- Question 3: How do I prove things about mutually inductive types?

theorem aboutThings :  t, sizeOfThing t * 2 = sizeOfObject (Box t t) - 10 :=
  by
  intro t
  -- induction t
  -- "induction t" here gives me the error:
  --      'induction' tactic does not support mutually inductive types,
  --      the eliminator 'Thing.rec' has multiple motives
  -- What can I use instead?
  -- "cases t" will not give me an induction hypothesis, right?
  sorry

Joachim Breitner (Oct 05 2023 at 16:55):

You meant Thing and Object, not Formula and Program, right?

Malvin Gattinger (Oct 05 2023 at 16:55):

Ah, yes, sorry, that was copypasta leftover from my original code.

Joachim Breitner (Oct 05 2023 at 16:59):

The unrelated import seems to be caused by

import Mathlib.Data.Nat.Order.Basic

at least I can observe your issue with that, but not if I replace it with that module’s imports.

Joachim Breitner (Oct 05 2023 at 17:01):

And if I change the result type of sizeOfThing from Nat to Unit the problem also goes away.

Malvin Gattinger (Oct 05 2023 at 17:02):

Ah, thanks for tracking that down. I also noticed that my code behaves differently when I used N\mathbb{N} instead of Nat, resulting in failed to synthesize instance OfNat ℕ 13 etc. Is that related?

Malvin Gattinger (Oct 05 2023 at 17:03):

Could it be that importing something brings extra lemmas or simp rules in scope which make the thing that tries to prove the termination / decreasing fail?

Joachim Breitner (Oct 05 2023 at 17:12):

This seems to work (although I wonder how that differs from the default):

mutual
  def sizeOfThing : Thing  Nat
    | Pizza => 13
    | round s => sizeOfThing s
    | Bag o => 1 + sizeOfObject o
  def sizeOfObject : Object  Nat
    | Bike => 42
    | Box t1 t2 => 10 + sizeOfThing t1 + sizeOfThing t2
end
termination_by
  sizeOfThing t => sizeOf t
  sizeOfObject o => sizeOf o

Malvin Gattinger (Oct 05 2023 at 17:14):

Oh, what is sizeOf here then? A default method counting levels of nested constructors?

Joachim Breitner (Oct 05 2023 at 17:17):

Yes, I think it is generated automatically whenever you define an inductive

Joachim Breitner (Oct 05 2023 at 17:17):

And here is how you can proof your theorem:

theorem aboutThings :  t, sizeOfThing t * 2 = sizeOfObject (Box t t) - 10 :=  by
  unfold sizeOfObject
  intro t
  rw [add_assoc,  Nat.mul_two, Nat.add_sub_self_left]

;-)

Joachim Breitner (Oct 05 2023 at 17:19):

Your comment “ -- "cases t" will not give me an induction hypothesis, right?” is actually going into a possibly right direction: You can use aboutThings inside aboutThings, it’ll be like a recursive function definition, and the system (or you) can prove termination, that works as a recursive proof.

Joachim Breitner (Oct 05 2023 at 17:21):

Like this (the lemma is not a good example, I guess):

theorem aboutThings :  t, sizeOfThing t * 2 = sizeOfObject (Box t t) - 10 := by
  intro t
  cases t with
  | Pizza => sorry
  | round t =>
    have := aboutThings t
    sorry
  | Bag _ => sorry

Joachim Breitner (Oct 05 2023 at 17:28):

I was expecting this to work:

mutual
theorem aboutThings :  t, sizeOfThing t % 2 = 1 := by
  intro t
  cases t with
  | Pizza => simp
  | round t =>
    have := aboutThings t
    unfold sizeOfThing
    exact this
  | Bag o =>
    unfold sizeOfThing
    rw [Nat.add_mod, aboutObjects]
    simp

theorem aboutObjects :  o, sizeOfObject o % 2 = 0 := by
  intro o
  cases o with
  | Bike => simp
  | Box t1 t2 =>
    unfold sizeOfObject
    rw [Nat.add_mod, aboutThings]
    rw [Nat.add_mod (a := 10), aboutThings]
    simp
end
termination_by
  aboutThings t => sizeOf t
  aboutObjects o => sizeOf o

but I get

(kernel) unknown constant 'aboutObjects'

Joachim Breitner (Oct 05 2023 at 17:30):

Ok, avoiding putting the recursive call inside rw avoids that bug:

mutual
theorem aboutThings :  t, sizeOfThing t % 2 = 1
  | Pizza => by simp
  | round t => by
    have := aboutThings t
    unfold sizeOfThing
    exact this
  | Bag o => by
    unfold sizeOfThing
    have := aboutObjects o
    rw [Nat.add_mod, this]
    simp

theorem aboutObjects :  o, sizeOfObject o % 2 = 0
  | Bike => by simp
  | Box t1 t2 => by
    unfold sizeOfObject
    have h1 := aboutThings t1
    have h2 := aboutThings t2
    rw [Nat.add_mod, h2]
    rw [Nat.add_mod (a := 10), h1]
    simp
end
termination_by
  aboutThings t => sizeOf t
  aboutObjects o => sizeOf o

Malvin Gattinger (Oct 05 2023 at 17:37):

Nice, thank you! Indeed the lemma was a silly example, as it can also be proven without induction.

Joachim Breitner (Oct 05 2023 at 17:37):

You can also write

    rw [Nat.add_mod, aboutThings t2]
    rw [Nat.add_mod (a := 10), aboutThings t1

as long as you pass the parameter explicitly.

Joachim Breitner (Oct 05 2023 at 17:53):

Reported the kernel error at https://github.com/leanprover/lean4/issues/2628

Joachim Breitner (Oct 05 2023 at 17:57):

And back to your original question: As far as I know, support for structural mutual recursion is planned, but not present. At the moment, it generates the sizeOf function structurally recursively, thus enabling you to define stuff using well-founded recursion (and even if your definition looks structurally recursive and gets accepted without further ado, the system uses well-founded recursion internally).
sizeOf is defined by way of Object.rec, and I assume you can use that directly, if you want to.

Malvin Gattinger (Oct 05 2023 at 17:57):

Note to self: DecidableEq for mutual inductives is apparently not yet doable but coming up in the next Lean version: https://github.com/leanprover/lean4/pull/2591

Joachim Breitner (Oct 05 2023 at 18:01):

Here is a proof using the recursor directly:

theorem aboutThings :  t, sizeOfThing t % 2 = 1 :=
  Thing.rec
    (motive_1 := fun t => sizeOfThing t % 2 = 1)
    (motive_2 := fun o => sizeOfObject o % 2 = 0)
    (Bag := fun o h => by
      dsimp
      unfold sizeOfThing
      rw [Nat.add_mod, h]
      rfl)
    (Bike := rfl)
    (Box := fun t h => by
      dsimp
      unfold sizeOfObject
      rw [Nat.add_mod, h]
      rfl)

Malvin Gattinger (Oct 23 2023 at 19:12):

Late thanks again for this. This example using a recuror direclty helped me in anoher case just now :smiley_cat:

Malvin Gattinger (Oct 23 2023 at 19:13):

Is it bad pratice to do something like apply Thing.rec in a tactic proof?


Last updated: Dec 20 2023 at 11:08 UTC