Zulip Chat Archive
Stream: lean4
Topic: simp unfolding let bindings
Eric Rodriguez (Aug 24 2023 at 11:17):
mwe:
example (a : Nat) : a + 1 - 1 = 23 := by
let x := a + 1 - 1
show x = _
simp only
sorry
I don't want Lean to unfold this let binding, and yet it is happening; I'd expect this if I did simp only [x]
. Is this expected behaviour or a bug? Is there any way to work around this?
Ruben Van de Velde (Aug 24 2023 at 11:26):
There is a config flag, but I always forget what it's called
Ruben Van de Velde (Aug 24 2023 at 11:27):
Is it simp (config { zeta := false })
?
Eric Rodriguez (Aug 24 2023 at 11:29):
having to write simp (config := ...)
for every single one is pretty awful, especially as this is a "destructive" change
Eric Rodriguez (Aug 24 2023 at 11:30):
Usually let bindings are quite short to hide long terms, so having to write simp [my_thing]
is not awful, if this is wanted behaviour
Eric Rodriguez (Aug 24 2023 at 11:30):
and now instead I'd have to set every single simp after initialising a let
to have zeta := false
?
Ruben Van de Velde (Aug 24 2023 at 11:44):
I didn't say I liked it :)
Alex J. Best (Aug 24 2023 at 12:15):
I agree that I don't much like this default, and it might be useful enough to have a mathlib tactic simp_no_zeta
that shortens this
Eric Wieser (Aug 24 2023 at 16:02):
simp {zeta := false}
would be ok IMO too
Eric Wieser (Aug 24 2023 at 16:03):
Or we could override the syntax of simp
in mathlib to change the default
Eric Rodriguez (Aug 24 2023 at 16:05):
I'd be strongly in favour with overriding the default in mathlib or petitioning for it to be changed in core. Are issues still discouraged for core?
Eric Wieser (Aug 24 2023 at 16:23):
/poll Which default behavior of simp would you prefer in mathlib
- zeta := true (default in Lean 4), unfolds lets
- zeta := false (default in Lean 3), does not unfold lets
Eric Wieser (Aug 24 2023 at 16:23):
(non-binding, but to get a feel for how skewed opinion is)
Ruben Van de Velde (Aug 24 2023 at 17:41):
One thing to note is that lean 4 does not support let x = ...; simp [x]
, so it'd be good to have a short syntax for that as well
Kyle Miller (Aug 24 2023 at 18:44):
As of today, mathlib now has unfold_let x
to do targeted zeta reduction.
Kyle Miller (Aug 24 2023 at 18:51):
Just so everyone knows simp (config := {zeta := false})
will preserve let
expressions, so if the goal is let x := 1; x = 1
then it will remain unchanged.
Kyle Miller (Aug 24 2023 at 18:53):
Maybe the zeta
configuration should be split into two, one that controls substitution of fvars (what the poll is about) and one that controls whether let expressions are reduced (which I presume the poll isn't about)
Alex J. Best (Aug 24 2023 at 20:22):
FWIW I very rarely end up with goals with let
s in explicitly, but have local decls quite often. (also we wish we could could do simp {zeta := false}
and not simp (config := {zeta:=False})
but currently cannot)
Eric Rodriguez (Aug 25 2023 at 11:25):
For those that voted for zeta being true, I'm curious as to what are your reasons, too
Eric Rodriguez (Aug 29 2023 at 15:05):
is it possible to at least make a quick macro so that this can be worked around for the moment? something like simpζ
:= simp
+ zeta := false
Eric Rodriguez (Aug 29 2023 at 15:05):
this currently makes some proofs unreadable in the tactic state...
Bolton Bailey (Aug 29 2023 at 16:48):
I was happy at one point to learn about set
which makes it easier to control how my bindings display at various times. This discussion might be related?
Patrick Massot (Sep 13 2023 at 18:00):
I was discussing simp
issues with Tomas over lunch and he reminded me of this thread (I voted in the poll but then forget). This allowed to write this fix which really looks like restoring sanity. Is there any progress on making that the default in Lean core? Or any explanation about why the Lean 4 behavior could be a desired default? @Sebastian Ullrich
Patrick Massot (Sep 13 2023 at 18:04):
And one more. simp (config := {zeta := false})
is quickly becoming my favorite tactic. It's long to type but it's really worth the trouble!
Patrick Massot (Sep 13 2023 at 18:06):
Lean 4 is becoming so easy it almost feels like cheating!
Jireh Loreaux (Sep 13 2023 at 18:19):
@Mario Carneiro and @Adam Topaz would you mind explaining why you prefer zeta := true
as the default?
Patrick Massot (Sep 13 2023 at 18:27):
To be honest I also just pushed this commit getting rid of many unfold_let
. I wrote those yesterday because let x := ... ; simp [x]
didn't work so I replaced them by let x := ... ; unfold_let x; simp;
. I know understand let x := ... ; simp
works. But really I very much prefer to have to tell simp
which definitions it should unfold since it is some much more compact and explicit. In a proving context let
is used to hide complexity. Undoing this work by default seems a really wrong choice.
James Gallicchio (Sep 13 2023 at 18:30):
I mean, I'd probably say have
hides complexity, and let
just makes it more compact... other than explicit unfold_let
, are the let-definitions frequently being unfolded in other parts of the proof? (maybe in defeq checks or something)
Patrick Massot (Sep 13 2023 at 18:32):
Sure they are also unfolded in defEq checks
Patrick Massot (Sep 13 2023 at 18:34):
And the difference between have
and let
is that one is for Prop and the other for data, so I don't understand what you mean.
James Gallicchio (Sep 13 2023 at 18:44):
You can have
non-prop values... the difference is purely in whether the definitional equality is visible, no?
Patrick Massot (Sep 13 2023 at 18:45):
You can but this is never what you want to do.
James Gallicchio (Sep 13 2023 at 18:47):
(at least in CS-world I do use this, not frequently but not rarely...)
Jireh Loreaux (Sep 13 2023 at 18:50):
@James Gallicchio really? I would be interested in seeing an example. You're saying that you need to use the fact that some piece of data has a particular type without actually caring what the thing is in particular? (sorry if I sound incredulous, I'm just surprised)
James Gallicchio (Sep 13 2023 at 18:52):
Let me look through the archives........
James Gallicchio (Sep 13 2023 at 18:54):
here's an example.. The idea is I build a proof while building a value, then I want to forget the full definition and have just the generated proof as the only "fact" about the value.
James Gallicchio (Sep 13 2023 at 18:56):
iirc it was have
instead of let
specifically to prevent tactics from expanding the definition (which is unwieldy).
James Gallicchio (Sep 13 2023 at 18:57):
of course I could have zeta:=false
'd at the callsites, but this clearly makes more sense to be thrown out at the definition site.
Patrick Massot (Sep 13 2023 at 19:03):
That's only supporting the idea that zeta := false
should be the default.
Patrick Massot (Sep 13 2023 at 19:04):
And I still find this example really weird (but I can't play with it since it isn't a #mwe).
Jireh Loreaux (Sep 13 2023 at 19:07):
I'm also very confused about what is happening here, I think in part because your Array
is not docs#Array
James Gallicchio (Sep 13 2023 at 19:10):
sorry, this is the first snippet grep found :big_smile: but yes, I'm not opposed to zeta:=false
, I know in math-applications you frequently go between wanting to expand let
s and not, whereas in CS-land I try to prove the properties I need and then close the definition permanently.
Scott Morrison (Sep 13 2023 at 23:50):
Perhaps someone can make a toolchain with the default changed so there is something more concrete to look at? (I think the preferred method today is to make a draft PR, and then wait an hour for the leanprover/lean4-pr-releases:pr-release-NNNN
toolchain is available.)
Patrick Massot (Sep 14 2023 at 22:18):
Scott, I would like to try that, and I created https://github.com/PatrickMassot/lean4/tree/simp_zeta_false, but I'm reluctant to open a draft PR since it doesn't seem to comply to the contributions instructions. Do you know whether there is a way to run CI for this branch in my fork? That would be a first step.
Matthew Ballard (Sep 14 2023 at 22:32):
I made some notes on my experience https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/home.20cooked.20release
Patrick Massot (Sep 15 2023 at 00:41):
@Matthew Ballard thanks but I have no idea how to implement your list.
Patrick Massot (Sep 15 2023 at 00:41):
Specifically "Make sure that both the Nix-CI and CI workflows are enabled to run. The first was fine but the second was turned off by default for me. "
Patrick Massot (Sep 15 2023 at 03:00):
I still have no idea how to run CI in my fork but I think I built Lean locally with that change without a single error.
Scott Morrison (Sep 15 2023 at 04:45):
As far as I understand, none of Matthew's list is needed, except the first step.
You enable actions by visiting:
https://github.com/<username>/lean4/settings/actions
After that you just push a tag to your fork.
Scott Morrison (Sep 15 2023 at 04:45):
(@Patrick Massot, you should add your github username to your zulip profile, as a good example. :-)
Patrick Massot (Sep 15 2023 at 12:54):
Scott, the point is that I see
image.png
and that doesn't seem to be good enough.
Matthew Ballard (Sep 15 2023 at 13:04):
That's what I have on mine. Just nothing happens when you push a tag?
Patrick Massot (Sep 15 2023 at 13:10):
Oh I didn't realize I needed to push a tag to my fork! It seems to work. Sorry about taking so long.
Matthew Ballard (Sep 15 2023 at 13:15):
Perfectly understandable.
Patrick Massot (Sep 16 2023 at 01:18):
I made progress: I have https://github.com/PatrickMassot/lean4/actions saying tests are successful, but PR release failed, complaining about a missing token.
Sebastian Ullrich (Sep 16 2023 at 11:10):
The PR release error is confusing and should be removed, but is ultimately harmless: https://github.com/PatrickMassot/lean4/releases/tag/zeta_false2
Matthew Ballard (Sep 16 2023 at 14:41):
Matthew Ballard said:
- The action for releasing can require additional permission set directly in the
.github/workflows/ci.yml
. This seems like a known issue and can be fixed by addingpermissions: contents: write
after the
on:
node in the file
Does this fix it?
Sebastian Ullrich (Sep 16 2023 at 20:47):
We simply should not attempt PR releases in forks /cc @Scott Morrison
Scott Morrison (Sep 17 2023 at 07:47):
Yep, thank you, that is my bug! lean4#2550
Patrick Massot (Sep 17 2023 at 21:40):
It turns out the zeta
config option isn't powerful enough to force simp
to preserve let bindings. See
def f : Nat → Nat := fun x ↦ x - x
@[simp]
theorem f_zero (n : Nat) : f n = 0 := Nat.sub_self n
example (n : Nat) : False := by
let g := f n
have : g + n = n := by
simp (config := {zeta := false}) [Nat.zero_add]
sorry
which happily ignores the option and closes the goal anyway. Compare to Lean 3:
def f : nat → nat := fun x, x - x
@[simp]
lemma f_zero (n : ℕ) : f n = 0 := nat.sub_self n
example (n : nat) : false :=
begin
let g := f n,
have : g + n = n,
{ -- dsimp only [g],
simp [nat.zero_add] },
sorry
end
which does not close the goal without the dsimp
line. @Heather Macbeth this is what prevents fixing the sphere instance proof, even with a patched version of field_simp
.
@Sebastian Ullrich any idea how we could get back a simp
that does not break through let
values?
Patrick Massot (Sep 17 2023 at 21:40):
Of course in my example this looks like a feature, but in actual example it prevents simp from closing the goal, by sending it to a dead end.
Eric Wieser (Sep 17 2023 at 21:59):
I think this might happen because even if it's not unfolded, g
is considered reducible?
Eric Wieser (Sep 17 2023 at 22:01):
simp (config := {
contextual := false
memoize := false
singlePass := true
zeta := false
beta := false
eta := false
iota := false
proj := false
decide := false
arith := false
autoUnfold := false})
also doesn't seem to help
Tomas Skrivan (Sep 17 2023 at 22:08):
Patrick Massot said:
any idea how we could get back a
simp
that does not break throughlet
values?
I think this would require somehow ensure that this isDefEq
can't see through let bindings.
When you traverse a term and there are let bindings then with zeta:=false
let variables are replaced with a fresh local free variable and thus simp can't see through them anymore. Something like this needs to be done to let bindings that are in the context at the start of simp run.
I see two options:
- have an option on isDefEq
not to unfold let bindings
- before running simp on an expression replace all let free variables with new fresh local free variables and after simp is done replace them back
Tomas Skrivan (Sep 17 2023 at 22:18):
There seems to be config setting zetaNonDep
in MetaM
/-- If zetaNonDep == false, then non dependent let-decls are not zeta expanded. -/
zetaNonDep : Bool := true
setting this to false
might do the trick
Kyle Miller (Sep 17 2023 at 22:26):
That setting controls whether to zeta expand local let bindings that have nonDep
set to true -- my understanding is that this value is not something you can count on unless your code is fully responsible for your local context.
Eric Wieser (Sep 17 2023 at 22:32):
It looks like revert g
prevents simp
unfolding it, which is consistent with Tomas' explanation
Tomas Skrivan (Sep 17 2023 at 22:37):
Kyle Miller said:
That setting controls whether to zeta expand local let bindings that have
nonDep
set to true -- my understanding is that this value is not something you can count on unless your code is fully responsible for your local context.
Yeah, a quick test reveals that it does not work
import Lean
import Qq
open Lean Meta Qq
#eval show MetaM Unit from do
withLetDecl `x q(Nat) q(10) fun x => do
let y := q(10:Nat)
IO.println s!"with `zetaNonDep := true` is x =?= y: {← isDefEq x y}"
let (xyEq,zetaFVars) ← withConfig (fun cfg => {cfg with zetaNonDep := false, trackZeta := true}) <| do
pure (← isDefEq x y, ← getZetaFVarIds)
IO.println s!"with 'zetaNonDep := false` is x =?= y: {xyEq}"
IO.println s!"expandend let fvars {← zetaFVars.toList.mapM (fun id => ppExpr (Expr.fvar id))}"
returns
with `zetaNonDep := true` is x =?= y: true
with 'zetaNonDep := false` is x =?= y: true
expandend let fvars [x]
I hoped that it would return with 'zetaNonDep := false' is x =?= y: false
There is an option to track which fvars are zeta expanded which seems to work as expected. Not sure if it can be helpful.
Kyle Miller (Sep 17 2023 at 22:40):
@Tomas Skrivan You can still set nonDep := true
for everything in the context
Tomas Skrivan (Sep 17 2023 at 22:41):
Kyle Miller said:
Tomas Skrivan You can still set
nonDep := true
for everything in the context
What does that do? And do you mean LocalDecl.ldecl.nonDep
?
Tomas Skrivan (Sep 17 2023 at 22:48):
Ok, setting nonDep := true
manually works, but as Kyle said it is probably not reliable
import Lean
import Qq
open Lean Meta Qq
#eval show MetaM Unit from do
withLetDecl `x q(Nat) q(10) fun x => do
let lctx := (← getLCtx).modifyLocalDecl x.fvarId!
fun decl =>
if let .ldecl i id name type val nonDep kind := decl then
.ldecl i id name type val true kind
else
decl
withLCtx lctx (← getLocalInstances) do
let y := q(10:Nat)
IO.println s!"with `zetaNonDep := true` is x =?= y: {← isDefEq x y}"
let (xyEq,zetaFVars) ← withConfig (fun cfg => {cfg with zetaNonDep := false, trackZeta := true}) <| do
pure (← isDefEq x y, ← getZetaFVarIds)
IO.println s!"with 'zetaNonDep := false` is x =?= y: {xyEq}"
IO.println s!"expandend let fvars {← zetaFVars.toList.mapM (fun id => ppExpr (Expr.fvar id))}"
prints
with `zetaNonDep := true` is x =?= y: true
with 'zetaNonDep := false` is x =?= y: false
expandend let fvars []
Kyle Miller (Sep 17 2023 at 22:50):
@Tomas Skrivan I just meant that you can't depend on it unless you were responsible for the values. Here's code that sets nonDep
for all local let bindings (which I think is OK to do?), and then you get the same output as your message.
code
Kyle Miller (Sep 17 2023 at 22:53):
(There's also a nonDep flag in let expressions themselves, and when you intro them I believe that's what sets the nonDep flag for the ldecl. I'm not sure exactly, but isDefEq might always do zeta reduction of lets, so it doesn't matter if the let expressions could have garbage nonDep values here?)
Tomas Skrivan (Sep 17 2023 at 22:58):
The nonDep
in let binding does not seem to affect isDefEq
let z := Expr.letE `z q(Nat) q(10) (.bvar 0) true
IO.println s!"with 'zetaNonDep := false` is y =?= z: {← isDefEq y z}}"
still returns true
Kyle Miller (Sep 17 2023 at 23:00):
Yeah, for example WHNF always zeta reduces lets
Tomas Skrivan (Sep 17 2023 at 23:18):
Here is demonstration that setting nonDep := true
to local context can fix simp. Not sure how reliably but it might work
I had to peel bunch of layers of simp, so mysimp
rather then closing the goal prints out the reduced term
code
with zetaNonDep := true
mysimp
prints out simp reduced goal to: True
i.e. solves to goal
with zetaNonDep := false
mysimp
prints out simp reduced goal to: g + n = n
i.e. leaves the goal unchanged
Tomas Skrivan (Sep 17 2023 at 23:21):
(How do I put the code into the collapsible box?)
Kyle Miller (Sep 17 2023 at 23:23):
```spoiler SomeCaption
````lean
example True := trivial
````
```
Eric Rodriguez (Oct 13 2023 at 13:20):
lean4#2682; I am told that :thumbs_up: on the issue mean something to the team.
Eric Rodriguez (Oct 28 2023 at 16:12):
Some feedback from Leo:
- Community wants us to split the
zeta
flag into two new flags:
zeta1 : Given
... x : ty := val ... |- C[x]
,simp
reduces it to... x : ty := val ... |- C[val]
zeta2 : Given
.... |- C[let x : ty := val; e[x]]
,simp
reduces it to... |- C[e[val]]
Please send suggestions to name the new flags.
- Community wants
zeta1 := false
andzeta2 := true
as the default setting forsimp
I think this would make most people happy, yes?
Eric Wieser (Oct 28 2023 at 16:22):
Is there any situation in which we want zeta1
but not zeta2
? If so, we could have zeta := true; zeta_ctx := true
for zeta1 && zeta2
, and zeta := true; zeta_ctx := false
for zeta2
only.
Scott Morrison (Oct 29 2023 at 22:55):
@Eric Wieser, given the proposed defaults, it seems the only effect of your suggestion is making it impossible to select zeta1 := true
, zeta2 := false
, in the (possibly unlikely) scenario that someone wants that.
(In particular, given the proposed defaults, in the likely scenarios your proposal doesn't actually reduce the amount of configuration by the user.)
Scott Morrison (Oct 29 2023 at 22:56):
How about we name
zeta2
as justzeta
(i.e. so sometimes people will usezeta := false
to completely turn of zeta reduction)zeta1
aszeta_ctx
(i.e. so sometimes people will usezeta_ctx := true
to additionally do zeta reduction in the context)
Kyle Miller (Oct 29 2023 at 23:18):
That seems reasonable to me. If someone does want to zeta reduce just local variables, there are thing like mathlib's unfold_let
tactic when it's given no arguments, or core's zeta
conv-mode tactic.
Kyle Miller (Oct 29 2023 at 23:19):
Maybe instead of zeta_ctx
it could be zeta_locals
?
Eric Rodriguez (Oct 29 2023 at 23:40):
The lean3 feature of doing simp [n]
to unfold the let binding for n
is back, BTW
Kyle Miller (Oct 29 2023 at 23:49):
Good to know, though unfold_let
without arguments will unfold all local variables, so if you want the missing configuration you can do unfold_let; simp (config := {zeta := false}) ...
Patrick Massot (Nov 07 2023 at 23:59):
I am confused by the new behavior of simp
in the presence of let
bindings in the local context. It seems that config := {zeta := false}
is now interpreted as: Lean shouldn't touch anything that is a let.
axiom abs : Int → Int
axiom abs_eq_self {a : Int }: abs a = a ↔ 0 ≤ a
example (x : Int) : False := by
let y := x/2
have h : 0 ≤ y := sorry
have : abs y = y := by
simp (config := {zeta := false}) only [abs_eq_self.mpr h] -- simp made no progress
sorry
Using generalize y = z at *
before the simp call make it work.
Eric Wieser (Nov 08 2023 at 00:01):
I assume you meant "shouldn't touch"
Patrick Massot (Nov 08 2023 at 00:02):
Yes, thanks Eric.
Eric Wieser (Nov 08 2023 at 00:03):
Isn't this what the Nevermind, I see that this should work without unfoldingzeta_ctx
option discussed above is supposed to solve? Presumably it doesn't exist yet
Floris van Doorn (Nov 08 2023 at 00:03):
The last comments on lean4#2682 are relevant. I think we'll soon get a zeta_ctx
flag that does the zeta-reductions in the goals, but doesn't do the ones that are local hypotheses.
Mario Carneiro (Nov 08 2023 at 00:03):
There is no zeta reduction involved in this example
Mario Carneiro (Nov 08 2023 at 00:04):
this looks like a bug to me
Eric Wieser (Nov 08 2023 at 00:04):
Thanks for making the same mistake as me, Floris, to make me feel better!
Mario Carneiro (Nov 08 2023 at 00:05):
put another way, I would expect simp (config := {zeta := false})
to act the same as clear_value at *; simp
and this isn't
Patrick Massot (Nov 08 2023 at 00:14):
I opened lean4#2843
Last updated: Dec 20 2023 at 11:08 UTC