Zulip Chat Archive

Stream: lean4

Topic: structural recursion fails


Bhavik Mehta (Apr 25 2024 at 05:44):

Here's my example:

variable {α : Type}
variable (r : α  α  Prop)

/-- `SymmGen r` is the symmetric relation generated by `r`. -/
inductive SymmGen : α  α  Prop
  | rel :  x y, r x y  SymmGen x y
  | symm :  x y, SymmGen x y  SymmGen y x

def MyRel : Nat  Nat  Prop := SymmGen fun x y => y = x + 2

theorem preserve_add' {a : Nat} :  {x y : Nat}, MyRel x y  MyRel (x + a) (y + a)
| _, _, SymmGen.rel _ _ h => SymmGen.rel _ _ (by rw [h, Nat.add_right_comm])
| _, _, SymmGen.symm _ _ h => SymmGen.symm _ _ (preserve_add' h)

I'd expect structural recursion to succeed in the final case, since I'm applying preserve_add' to a structurally smaller term. Using the induction tactic works, but why does the equation compiler fail to recognise this is structural recursion?

Yaël Dillies (Apr 25 2024 at 05:46):

cc @Joachim Breitner

Joachim Breitner (Apr 25 2024 at 06:49):

I haven't touched the code that compiles structural recursion over inductive predicates (which works differently than when recursing over inductive types) yet, so hard to tell before breakfast :-)

@Daniel Fabian, who authored that code, might be able to debug this better.

Joachim Breitner (Apr 25 2024 at 06:53):

Does inlining MyRel help?

Bhavik Mehta (Apr 25 2024 at 06:57):

Joachim Breitner said:

I haven't touched the code that compiles structural recursion over inductive predicates (which works differently than when recursing over inductive types) yet, so hard to tell before breakfast :-)

Ahh okay, then I don't feel as bad for thinking that structural recursion on inductive predicates didn't get better with your change!

Bhavik Mehta (Apr 25 2024 at 06:57):

Inlining MyRel doesn't make a difference

Joachim Breitner (Apr 25 2024 at 07:20):

My changes so far only affected well-founded recursion

Joachim Breitner (Apr 25 2024 at 07:21):

Anyways, this looks like a bug worth opening an issue for.

Joachim Breitner (Apr 25 2024 at 07:50):

Based on the available trace options, the issue is somewhere in

def findBelowIdx (xs : Array Expr) (motive : Expr) : MetaM $ Option (Expr × Nat) := do
  xs.findSomeM? fun x => do
  let xTy  inferType x
  xTy.withApp fun f _ =>
  match f.constName?, xs.indexOf? x with
  | some name, some idx => do                                                                       if ( isInductivePredicate name) then
      let (_, belowTy)  belowType motive xs idx                                                      let below  mkFreshExprSyntheticOpaqueMVar belowTy
      try
        trace[Meta.IndPredBelow.match] "{←Meta.ppGoal below.mvarId!}"
        if ( backwardsChaining below.mvarId! 10) then
          trace[Meta.IndPredBelow.match] "Found below term in the local context: {below}"
          if ( xs.anyM (isDefEq below)) then pure none else pure (below, idx.val)                      else
          trace[Meta.IndPredBelow.match] "could not find below term in the local context"
          pure none
      catch _ => pure none
    else pure none
  | _, _ => pure none

Jonathan Chan (May 13 2024 at 02:37):

I also have a failing structural recursion on the following, it's the same issue right

variable {Tm : Type}

inductive Steps (Step : Tm  Tm  Prop): Tm  Tm  Prop :=
  | refl a : Steps Step a a
  | trans {a b c} : Step a b  Steps Step b c  Steps Step a c

theorem stepsTrans {Step} {a b c : Tm} (r₁ : Steps Step a b) (r₂ : Steps Step b c) : Steps Step a c :=
  match r₁ with
  | Steps.refl _ => r₂
  | Steps.trans r r₁ => Steps.trans r (stepsTrans r₁ r₂)

Was the issue ever opened? Is there a link to it?

Kim Morrison (May 13 2024 at 02:51):

I guess not.

Bhavik Mehta (Jun 23 2024 at 13:52):

Oops, sorry, I forgot about this. Here's an issue for it: https://github.com/leanprover/lean4/issues/4540


Last updated: May 02 2025 at 03:31 UTC