leanprover / lean4

Lean 4 programming language and theorem prover
https://lean-lang.org
Apache License 2.0
4.49k stars 389 forks source link

RFC: simp to detect, report and ignore inherently looping rewrite rules #5111

Open nomeata opened 3 weeks ago

nomeata commented 3 weeks ago

Motivation

The simplifier is not very helpful when giving a non-terminating simp set:

variable (P : Nat → Prop)
/--
error: tactic 'simp' failed, nested error:
maximum recursion depth has been reached
use `set_option maxRecDepth <num>` to increase limit
use `set_option diagnostics true` to get diagnostic information
-/
#guard_msgs in
example (a b c : Nat) : P (a + b + c) := by simp [Nat.add_assoc, (Nat.add_assoc _ _ _).symm]

inductive Tree (α : Type) where | node : α → List (Tree α) → Tree α
def Tree.children : Tree α → List (Tree α) | .node _ ts => ts
def Tree.size (t : Tree α) := 1 + Nat.sum (t.children.attach.map (fun ⟨c,_⟩  => c.size))
decreasing_by simp_wf; cases t; simp_all [Tree.children]; decreasing_trivial

/--
error: tactic 'simp' failed, nested error:
maximum recursion depth has been reached
use `set_option maxRecDepth <num>` to increase limit
use `set_option diagnostics true` to get diagnostic information
-/
#guard_msgs in
example (t : Tree α) : 0 < t.size := by simp [Tree.size]

The simplifier only says that it ran too far, but gives very little help for debugging this. The newish set_option diagnostics true can help, but still needs careful investigation – if lemma foo is the bad looping one, but in each step some innocent other lemmas are used a few times, foo will appear rather low on the list.

I assume that users face this not uncommonly in the two situations outlined above:

Goal

Instead of the unspecific error message above I’d like simp to

Of course not all kinds of simp loops can be prevented, but many common and “obvious” ones can.

Proposal

My idea for how to address this is based on the observation that a good™ simp lemma likely has it's RHS in simp-normal form, because what’s the point in not. But in the two examples above, the bad™ rules do not have their RHS in simp-normal form!

So the idea is that simp would, before applying a lemma ∀ x, lhs[x] = rhs[x] to an occurrence lhs[e], first simplify rhs[x] in the abstract, i.e. before instantiating x, to rhs'[x], and then continue with rhs'[e].

If simplifying the abstract rhs[x] already fails (by hitting the recursion limit), we conclude that this is not a good rule for simp to apply, report it, and ignore it from now on.

Even better: We don’t even have to hit the recursion limit. As we simplify the rule’s RHS with other rules we do the same thing; simplify these rules’s RHS first. If we keep track of the stack of rules which we are currently simplifying we can abort as soon as we look at a rule that we looked at before, as we just ran into a circle, and can very precisely report the set of looping rules.

Notes

Community Feedback

(None yet)

Impact

Add :+1: to issues you consider important. If others benefit from the changes in this proposal being added, please ask them to add :+1: to it.

semorrison commented 3 weeks ago

"and then continue with rhs'[x]." should be rhs'[e].

This would be nice. :-)