Zulip Chat Archive

Stream: general

Topic: Reducibility and decreasing_by


Violeta Hernández (Feb 28 2025 at 04:15):

While trying to minimize a related issue related to decidable instances, I came across the following odd behavior:

import Mathlib.Data.Nat.Basic

def foo (_m _n : ) : Prop := True

theorem notSorry (m n : ) (h : m  0) : Prod.Lex (· < ·) (· < ·) (m - 1, n) (m, n) := by
  apply Prod.Lex.left
  exact Nat.pred_lt h

@[reducible]
def decFoo {m n : } : Decidable (foo m n) :=
  if m = 0 then isTrue ⟨⟩ else
    letI : Decidable (foo (m - 1) n) := decFoo
    decidable_of_iff (foo (m - 1) n) Iff.rfl
termination_by (m, n)
decreasing_by apply notSorry; assumption

instance : DecidableRel foo := @decFoo

example : foo 2 2 := by decide

Here, foo 2 2 is successfully proven. But if we replace the code within decreasing_by by sorry, it fails.

Is this a known/intended behavior? I would have imagined that since proofs are erased, any sorry (or whatever other axiom) would be ignored while reducing the decidability instance.

Violeta Hernández (Feb 28 2025 at 04:17):

(Even more perplexingly, if we get rid of the second argument n throughout, the decide works with or without a sorry)

Violeta Hernández (Feb 28 2025 at 04:51):

Came up with a simpler example, this one confuses me even more

def bar (n : Nat) : Prop := True

noncomputable def decBar (n : Nat) : Decidable (bar n) :=
  WellFounded.fix (r := (· < ·)) Nat.lt_wfRel.wf (fun _ _  isTrue ⟨⟩) n

noncomputable instance : DecidablePred bar := decBar

-- This works?
example : bar 5 := by decide

Violeta Hernández (Feb 28 2025 at 04:52):

How can this possibly work if Lean also complains about how decBar is noncomputable?

Violeta Hernández (Feb 28 2025 at 04:52):

And, how come this breaks if you replace Nat.lt_wfRel.wf by sorry?

Violeta Hernández (Feb 28 2025 at 05:01):

And also how come Lean doesn't complain about computability when using termination_by but it does complain here?

suhr (Feb 28 2025 at 09:12):

Violeta Hernández said:

How can this possibly work if Lean also complains about how decBar is noncomputable?

noncomputable does not prevent reduction, it only matters for code generation.

Joachim Breitner (Mar 01 2025 at 16:31):

When reducing a definition defined by well founded recursion, the kernel does have to reduce the termination proof, and may get stuck on the sorry axiom. Proofs erasure is a concept of the compiler, not the kernel and it's concept of reduction.

Generally I'd strongly suggest to not rely on the reduction behavior of well-founded recursion, and use rewriting (simp, rw, unfold) instead of rfl or decide.

Violeta Hernández (Mar 01 2025 at 21:54):

That's a bit unfortunate given I wanted to prove decidability of a relation defined through well-founded recursion.

François G. Dorais (Mar 02 2025 at 05:10):

Joachim Breitner said:

Generally I'd strongly suggest to not rely on the reduction behavior of well-founded recursion, and use rewriting (simp, rw, unfold) instead of rfl or decide.

Don't simp and rw use rfl sometimes?

Sebastian Ullrich (Mar 02 2025 at 08:20):

Recall that wfrec defs are irreducible by default

Joachim Breitner (Mar 02 2025 at 11:29):

Violeta Hernández said:

That's a bit unfortunate given I wanted to prove decidability of a relation defined through well-founded recursion.

You can still prove it, in a way, but if you then want to efficiently decide relations using decide you’ll have to work around this limitation.

You can use by native_decide if you are fine with the caveats. Then you can get very efficient decision procedures.

The next best option is to define a variant of your function, provably equivlaent, that uses Nat.rec with lots of fuel, and define the Decidable instance using that. Something along these lines

def lots_of_fuel : Nat := 9223372036854775807

def rsimp_iterate {α : Sort u} (x : α) (f : α  α) : α :=
  Nat.rec x (fun _ ih => f ih) lots_of_fuel

theorem reduce_with_fuel {α : Sort u} {x : α} {f : α  α} (h : x = f x) :
    x = rsimp_iterate x f := by
    unfold rsimp_iterate
    exact Nat.rec rfl (fun _ ih => h.trans (congrArg f ih)) lots_of_fuel

def Nat.gcd' : (n m : Nat)  Nat :=
  rsimp_iterate Nat.gcd fun gcd m n =>
    if m = 0 then n else gcd (n % m) m

theorem Nat.gcd'_eq_gcd : Nat.gcd = Nat.gcd' :=
  reduce_with_fuel Nat.gcd.eq_unfold

Last updated: May 02 2025 at 03:31 UTC