Exponential unfolding behaviour in Meta.isDefEq and Meta.whnf reduction
Nobody has claimed this yet.
- Dominant language
- Lean
- Stars
- 9.2k
- Forks
- 990
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 175
Description
Prerequisites
Please put an X between the brackets as you perform the following steps:
- Check that your issue is not already filed:
https://github.com/leanprover/lean4/issues - Reduce the issue to a minimal, self-contained, reproducible test case.
Avoid dependencies to Mathlib or Batteries. - Test your test case against the latest nightly release, for example on
https://live.lean-lang.org/#project=lean-nightly
(You can also use the settings there to switch to “Lean nightly”)
Description
In the following reduction with a definition of mergeWith that has been adapted from mergeWith from Batteries.RBSet onto List, there seems to an exponential number of reductions with respects to the size of the first or second list.
def _root_.List.mergeWith {α β} [BEq α] (t₁ t₂ : List (α × β)) : List (α × β) :=
t₂.foldl (init := t₁) fun t₁ a₂ =>
t₁.cons <| match t₁.find? (a₂.1 == ·.1) with | some a₁ => a₁ | none => a₂
/--
info: [reduction] unfolded declarations (max: 32752, num: 1):
[reduction] Nat.rec ↦ 32752[reduction] unfolded reducible declarations (max: 32752, num: 1):
[reduction] Nat.casesOn ↦ 32752use `set_option diagnostics.threshold <num>` to control threshold for reporting counters
-/
#guard_msgs(info) in
set_option diagnostics true in
set_option diagnostics.threshold 32000 in
example (T : Nat) : [(2, T)].mergeWith (List.replicate 13 (1, 0))
= (List.replicate 13 (1, 0)).concat (2, T) := by rfl
Increasing the size of the list to 14 in List.replicate will double the number of reductions of Nat.rec to ~64000 and it quickly becomes intractible. I feel like it's due to the same expression being reduced multiple times and not being cached/shared properly during the reduction.
One thing to note is that this is using a free variable T : Nat to force use of Meta.isDefEq instead of Kernel.isDefEq, because the latter doesn't seem to suffer from the same issue.
Context
We are using merging of Batteries.RBMap to build up a circuit from a description, and we were having trouble proving definitional equality using eq_refl. A link to a relevant discussion is here: Link to Zulip conversion. We then took the advice and modified eq_refl to use Kernel.isDefEq even with the presence of free variables, which seems to work well.
However, we are facing more issues because we are typing terms using elements of the map, and this leads to unwanted (and slow) reduction everywhere (with Meta.whnf this time), including in elaboration and delaboration.
Steps to Reproduce
- Open link to example, and see that currently
Nat.recis unfolded ~32000 times. - Change 13 to 14 in
List.replicateand see that the number of unfolds doubles.
Expected behavior: I would expect a polynomial number of unfolds as the size of the list increases.
Actual behavior: Instead, the number of unfolds seems to be exponential in the size of the lists.
Versions
4.12.0-nightly-2024-10-02
Impact
Add 👍 to issues you consider important. If others are impacted by this issue, please ask them to add 👍 to it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked live.lean.org reproduction and compare the diagnostics for 13 and 14 elements in List.replicate. Investigate the Meta.isDefEq and Meta.whnf reduction paths described in the report. Done means the reduction count grows polynomially rather than doubling exponentially for this example.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100