-
Notifications
You must be signed in to change notification settings - Fork 715
refactor: construct .eq_def before .eq_n
#5669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Mathlib CI status (docs):
|
This adds a new simp configuration flag: `underLambda`, on by default. Turning this off will make `simp` not rewrite under lambdas (and not in the branches of an `if-then-else`). This can can be useful when one wants to simplify in a way that more closely matches call-by-value execution, and can help with `simp [f]` where `f` is a function defined by well-founded recursion, and where without the flag the simplifier would go into a loop or produce huge proof terms. Preventing simplification under lambda is straight-forward (in `simpLambda`. Recognizing all the places where we might descent into the branches of if-then-else is a bit more work.
.eq_def before .eq_n.eq_def before .eq_n
…lean4 into joachim/issue5667
|
I gave this another shot, storing some of my observations here. I figured it might be more robust to prove the equational theorems if we had an unfolding lemma for The First big question: Can we write metaprogram that generate all these definitions and proofs, even for much more complex data types. (Especially With that, it seems we can prove unfolding lemmas a bit more easily: Anyways, it's clearly not a simple option to go that route, so shelving that idea here. |
ca8ae18 to
f325ac7
Compare
|
Just had another stab at this, wondering if after #10415 this (proving the unfolding lemma directly, using the existing machinery for proving the equational theorems) works better now. But it again fails already with code like The problem is in I wonder if I can have- or let-bind the recursive |
|
Ah, there is a much easier way that doesn’t require the I should turn this into a RFC. Or just do it. Hmm, although that still exposes the |
|
Here's another idea, inspired by how inductive predicate def Nat.brec.below.{u} {motive : Nat → Sort u} (F_1 : (t : Nat) → @Nat.below motive t → motive t) : ∀ t : Nat, @Nat.below motive t :=
@Nat.rec (@Nat.below motive) ⟨⟩ (fun _ ih => ⟨F_1 _ ih, ih⟩)
def Nat.brec.{u} {motive : Nat → Sort u} (F_1 : (t : Nat) → @Nat.below motive t → motive t) : ∀ t : Nat, motive t :=
fun t => F_1 t (@Nat.brec.below motive F_1 t)
def Nat.add2 (x y : Nat) : Nat :=
@Nat.brec (fun _ => Nat → Nat)
(fun x f x_2 =>
Nat.add.match_1 (fun _ x => @Nat.below (fun _ => Nat → Nat) x → Nat) x_2 x (fun a _ => a)
(fun a _ x => (x.1 a).succ) f)
y x
theorem Nat.add2.my_eq_def (x y : Nat) : x.add2 y = Nat.add.match_1 (fun _ _ => Nat) x y (fun a => a) fun a b => (a.add2 b).succ := by
delta Nat.add2
conv => lhs; delta brec; dsimp only
split
· rfl
· rflOne nice side effect is that stuff like this works with that idea: def testMe (n : Nat) (b : Bool) : Nat :=
if b then
3
else
match n with
| 0 => 27
| k + 1 => testMe k !b
set_option smartUnfolding false
example : testMe n true = 3 := by
fail_if_success rfl -- currently fails, but works with `F_1` on the outside
cases n <;> rfl |
|
What's the idea here in prose? I'm too tired to see it from the code right now |
|
And do the desired definitional equalities still hold, e.g. |
|
The idea is to create an application of |
Yes (not this one but): theorem Nat.add2_eq1 (a : Nat) : Nat.add2 a .zero = a := rfl
theorem Nat.add2_eq2 (a b : Nat) : Nat.add2 a b.succ = (a.add2 b).succ := rflboth work. It seems like |
|
Ah, right, wrong argument… I’m surprised it's slower. Do you have an idea why? In the kernel or the elaborator? Are you comparing with “the” |
|
I actually looked into it a bit more and the results are actually quite surprising: Test with results
import Lean
inductive MyNat where
| zero
| succ (n : MyNat)
open Lean Elab Meta Term Tactic
elab "share% " e:term : term => do
let e ← elabTerm e ‹_›
let env ← getEnv
let e := e.replace fun e => do
let .const fn _ := e.getAppFn | none
let info ← env.getProjectionFnInfo? fn
let args := e.getAppArgs
if h : info.numParams < args.size then
let some (.ctorInfo c) := env.find? info.ctorName | none
return mkAppN (.proj c.induct info.i args[info.numParams]) (args.extract (info.numParams + 1))
none
return ShareCommon.shareCommon' e
@[noinline, nospecialize]
def measureHeartbeats (x : MetaM α) : MetaM (α × Nat) := do
let t1 ← IO.getNumHeartbeats
let res ← x
let t2 ← IO.getNumHeartbeats
return (res, t2 - t1)
elab "test" : tactic => do
let goal ← getMainGoal
let mkApp3 (.const ``Eq us) α lhs rhs ← goal.getType | throwError "invalid goal"
modifyThe Lean.Meta.State fun state => { state with cache := {} }
let (b, t1) ← measureHeartbeats (isDefEq lhs rhs)
let (res, t2) ← measureHeartbeats (return Kernel.isDefEq (← getEnv) (← getLCtx) lhs rhs)
let .ok b' := res | throwError "kernel error"
unless b && b' do
throwError "not defeq"
Lean.logInfo m!"{t1} and {t2}"
goal.assign (mkApp2 (.const ``Eq.refl us) α lhs)
set_option linter.unusedVariables false
noncomputable abbrev MyNat.brec.below.{u} : ∀ {motive : MyNat → Sort u} (F_1 : (t : MyNat) → @MyNat.below motive t → motive t),
∀ t : MyNat, @MyNat.below motive t :=
@(share% fun {motive} F_1 => @MyNat.rec (@MyNat.below motive) ⟨⟩ (fun _ ih => ⟨F_1 _ ih, ih⟩))
noncomputable abbrev MyNat.brec.{u} : ∀ {motive : MyNat → Sort u} (F_1 : (t : MyNat) → @MyNat.below motive t → motive t), ∀ t : MyNat, motive t :=
@(share% fun {motive} F_1 => fun t => F_1 t (@MyNat.brec.below motive F_1 t))
noncomputable def MyNat.brec'.{u} : ∀ {motive : MyNat → Sort u} (F_1 : (t : MyNat) → @MyNat.below motive t → motive t), ∀ t : MyNat, motive t :=
@(share% fun {motive} F_1 => fun t => F_1 t (@MyNat.rec (@MyNat.below motive) ⟨⟩ (fun _ ih => ⟨F_1 _ ih, ih⟩) t))
-- basic variant with built-in support
def MyNat.add (x y : MyNat) : MyNat :=
match x, y with
| x, .zero => x
| x, .succ y => (x.add y).succ
-- sanity check, equivalent to `MyNat.add`
noncomputable def MyNat.add2 (x y : MyNat) : MyNat :=
share% @MyNat.brecOn (fun _ => MyNat → MyNat) y
(fun y f x =>
MyNat.add.match_1 (fun _ y => @MyNat.below (fun _ => MyNat → MyNat) y → MyNat) x y (fun a _ => a)
(fun a _ x => (x.1 a).succ) f)
x
-- using `brec`
noncomputable def MyNat.add3 (x y : MyNat) : MyNat :=
share% @MyNat.brec (fun _ => MyNat → MyNat)
(fun x f x_2 =>
MyNat.add.match_1 (fun _ x => @MyNat.below (fun _ => MyNat → MyNat) x → MyNat) x_2 x (fun a _ => a)
(fun a _ x => (x.1 a).succ) f)
y x
-- using `brec'`
noncomputable def MyNat.add4 (x y : MyNat) : MyNat :=
share% @MyNat.brec' (fun _ => MyNat → MyNat)
(fun x f x_2 =>
MyNat.add.match_1 (fun _ x => @MyNat.below (fun _ => MyNat → MyNat) x → MyNat) x_2 x (fun a _ => a)
(fun a _ x => (x.1 a).succ) f)
y x
set_option smartUnfolding false
/-- info: 1265 and 230 -/
#guard_msgs in example : MyNat.add x .zero = x := by test
/-- info: 1265 and 230 -/
#guard_msgs in example : MyNat.add2 x .zero = x := by test
/-- info: 1444 and 135 -/
#guard_msgs in example : MyNat.add3 x .zero = x := by test
/-- info: 1542 and 192 -/
#guard_msgs in example : MyNat.add4 x .zero = x := by test
/-- info: 2387 and 553 -/
#guard_msgs in example : MyNat.add x (.succ y) = .succ (MyNat.add x y) := by test
/-- info: 2380 and 553 -/
#guard_msgs in example : MyNat.add2 x (.succ y) = .succ (MyNat.add2 x y) := by test
/-- info: 6024 and 763 -/
#guard_msgs in example : MyNat.add3 x (.succ y) = .succ (MyNat.add3 x y) := by test
/-- info: 5014 and 620 -/
#guard_msgs in example : MyNat.add4 x (.succ y) = .succ (MyNat.add4 x y) := by test
/-- info: 2415 and 403 -/
#guard_msgs in example : MyNat.add .zero (.succ .zero) = .succ .zero := by test
/-- info: 2414 and 403 -/
#guard_msgs in example : MyNat.add2 .zero (.succ .zero) = .succ .zero := by test
/-- info: 2992 and 493 -/
#guard_msgs in example : MyNat.add3 .zero (.succ .zero) = .succ .zero := by test
/-- info: 2868 and 359 -/
#guard_msgs in example : MyNat.add4 .zero (.succ .zero) = .succ .zero := by test
/-- info: 3453 and 569 -/
#guard_msgs in example : MyNat.add .zero (.succ (.succ .zero)) = .succ (.succ .zero) := by test
/-- info: 3452 and 569 -/
#guard_msgs in example : MyNat.add2 .zero (.succ (.succ .zero)) = .succ (.succ .zero) := by test
/-- info: 4194 and 655 -/
#guard_msgs in example : MyNat.add3 .zero (.succ (.succ .zero)) = .succ (.succ .zero) := by test
/-- info: 4049 and 519 -/
#guard_msgs in example : MyNat.add4 .zero (.succ (.succ .zero)) = .succ (.succ .zero) := by test
/-- info: 4494 and 735 -/
#guard_msgs in example : MyNat.add .zero (.succ (.succ (.succ .zero))) = .succ (.succ (.succ .zero)) := by test
/-- info: 4495 and 735 -/
#guard_msgs in example : MyNat.add2 .zero (.succ (.succ (.succ .zero))) = .succ (.succ (.succ .zero)) := by test
/-- info: 5379 and 815 -/
#guard_msgs in example : MyNat.add3 .zero (.succ (.succ (.succ .zero))) = .succ (.succ (.succ .zero)) := by test
/-- info: 5235 and 679 -/
#guard_msgs in example : MyNat.add4 .zero (.succ (.succ (.succ .zero))) = .succ (.succ (.succ .zero)) := by test
/-- info: 5544 and 901 -/
#guard_msgs in example : MyNat.add .zero (.succ (.succ (.succ (.succ .zero)))) = .succ (.succ (.succ (.succ .zero))) := by test
/-- info: 5542 and 901 -/
#guard_msgs in example : MyNat.add2 .zero (.succ (.succ (.succ (.succ .zero)))) = .succ (.succ (.succ (.succ .zero))) := by test
/-- info: 6567 and 975 -/
#guard_msgs in example : MyNat.add3 .zero (.succ (.succ (.succ (.succ .zero)))) = .succ (.succ (.succ (.succ .zero))) := by test
/-- info: 6424 and 839 -/
#guard_msgs in example : MyNat.add4 .zero (.succ (.succ (.succ (.succ .zero)))) = .succ (.succ (.succ (.succ .zero))) := by test
/-- info: 9695 and 1565 -/
#guard_msgs in example : MyNat.add .zero (.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero)))))))) =
.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero))))))) := by test
/-- info: 9694 and 1565 -/
#guard_msgs in example : MyNat.add2 .zero (.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero)))))))) =
.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero))))))) := by test
/-- info: 11345 and 1615 -/
#guard_msgs in example : MyNat.add3 .zero (.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero)))))))) =
.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero))))))) := by test
/-- info: 11212 and 1479 -/
#guard_msgs in example : MyNat.add4 .zero (.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero)))))))) =
.succ (.succ (.succ (.succ (.succ (.succ (.succ (.succ .zero))))))) := by testIt seems like the new |
|
Thanks for investigating! You turn The kernel perf numbers vary a bit unpredicable between whether there are many Maybe it’s worth getting #10606 to work to the point where we can run |
|
Closing this in favor of #10606 |
I have a theory that we’d get less “failed to generate equational theorem” like in #5667 if we first generate the
.unfoldtheorem (a bit easier, no hypotheses to consider), and then use that to define the.eq_nthat don't hold byrflanyways.This (stashed, partial) work explores that direction, and shows that indeed it fixes #5667.
But of course plenty of other programs break. This
brecOnbusiness is not so easy!