Zulip Chat Archive
Stream: lean4
Topic: let this
Reid Barton (May 29 2023 at 16:33):
How come let this
makes a local variable without a definition?
def x : { n : Nat // n = 1 + 1 } := by
let this : Nat := 2
exact ⟨this, rfl⟩
It works with a different name.
Ruben Van de Velde (May 29 2023 at 16:41):
I think there's a general issue where this
needs to be special cased leading to weird inconsistencies
Eric Wieser (May 29 2023 at 16:49):
My understanding is that this is by design in some way
Mario Carneiro (May 29 2023 at 16:59):
the weird inconsistencies are not by design, but they are a consequence of the design
Reid Barton (May 29 2023 at 17:35):
The design seems buggy.
Mac Malone (May 30 2023 at 00:41):
this
is a pseudo-keyword, which I suspect is the problem here.
Mac Malone (May 30 2023 at 00:44):
For reference, the this
keyword definition: https://github.com/leanprover/lean4/blob/5661b15e35285a4ed11e0d1d70a960117ea501a9/src/Init/Notation.lean#L499-L503
Mac Malone (May 30 2023 at 00:48):
In fact, the whole point of that definition was to forbid bindings like (this : Nat)
, but it doesn't work in the case of let
presumably because let
permits arbitrary terms in its LHS (which are in turn presumably expanded leading to the keyword this
becoming the identifier this
and the binding working as normal).
Mario Carneiro (May 30 2023 at 08:39):
In fact, the whole point of that definition was to forbid bindings like (this : Nat),
That may be the effect but it wasn't the point of the design. The reason this
was made a keyword was so that it does not participate in hygiene, so that `(have := $e) => `(have this := $e)
does "what you expect". In practice I think the breakage far outweighs the benefit of being able to write this instead of `(have $(mkIdent `this) := $e)
to explicitly make an unhygienic identifier
Reid Barton (May 30 2023 at 08:47):
FWIW what happened to me here was:
- originally wrote a
have := blah blah
, later usethis
- realize that I need to use the definition so change
have
tolet
let := blah blah
isn't allowed any more for some reason so change it tolet this := blah blah
- it still doesn't work
- eventually notice that
let
isn't performing its actual function of making a definition...
Sebastian Ullrich (May 30 2023 at 09:09):
Mario Carneiro said:
`(have $(mkIdent `this) := $e)
to explicitly make an unhygienic identifier
I think we've before gone through how that doesn't work in quoted tactics. The two consistent solution I can see are:
- tweak the current implementation by disallowing
this
in patterns, e.g. by making it an elaborator - globally make
this
a special, hygiene-free identifier by specializing it in the hygiene algorithm
The latter is closer to Lean 3's behavior as it allows this
to be bound by anyone
Mario Carneiro (May 30 2023 at 09:15):
I think we've before gone through how that doesn't work in quoted tactics.
I don't think we have. Can you show an example?
Mario Carneiro (May 30 2023 at 09:16):
(We have gone through this issue before but not issues with mkIdent `this
)
Mario Carneiro (May 30 2023 at 09:19):
The latter is closer to Lean 3's behavior as it allows
this
to be bound by anyone
I disagree with this: in lean 3 this
was not special in any way. It is just an identifier named this
, that happened to be the default name used by some tactics. The reason for the unhygienic stuff was simply because everything is unhygienic in lean 3. But I do not believe that any solution that makes this
special will work, because this
is not the only variable name which has to deal with this issue. h
and H
are also used at various points in lean 3, and suffer from the same issues
Mario Carneiro (May 30 2023 at 09:22):
I think that the tactic should be the one to opt in to hygiene-less behavior for a particular identifier. If mkIdent `this
doesn't work, what about a no-hygiene hygiene you can put on an identifier? For example unhygienicIdent `this
which expands to this._@._unhygienic
and is handled appropriately by the hygiene manipulation functions
Sebastian Ullrich (May 30 2023 at 09:23):
Mario Carneiro said:
I think we've before gone through how that doesn't work in quoted tactics.
I don't think we have. Can you show an example?
macro "have2" e:term : tactic =>
`(tactic| have $(mkIdent `this2):ident := $e)
example : Nat := by have2 0; exact this2
macro "foo" : tactic =>
`(tactic| (have2 0; exact this2))
example : Nat := by foo -- unknown identifier 'this2'
Mario Carneiro (May 30 2023 at 09:23):
that's expected behavior though
Mario Carneiro (May 30 2023 at 09:23):
foo
would need to do mkIdent `this2
as well
Mario Carneiro (May 30 2023 at 09:24):
this is a good thing, because in foo
this2
might be a regular variable
Mario Carneiro (May 30 2023 at 09:25):
treating a variable as hygieneless should be opt-in
Sebastian Ullrich (May 30 2023 at 09:26):
It doesn't look reasonable to me at all. We should expect tactic sequences to be liftable into macros without randomly breaking. I don't expect most users to be able to implement have2
, but simple reuse like foo
should ideally be accessible to anyone
Sebastian Ullrich (May 30 2023 at 09:27):
Mario Carneiro said:
I think that the tactic should be the one to opt in to hygiene-less behavior for a particular identifier. If
mkIdent `this
doesn't work, what about a no-hygiene hygiene you can put on an identifier? For exampleunhygienicIdent `this
which expands tothis._@._unhygienic
and is handled appropriately by the hygiene manipulation functions
OTOH that sounds reasonable to me at a first glance
Mario Carneiro (May 30 2023 at 09:28):
that would also not fix the example you just showed, since this2
in foo
is a regular identifier and would get hygiened separately
Mario Carneiro (May 30 2023 at 09:29):
In practice I really don't see your example as being an issue. We would just tell someone confused about the situation to use have this := ...
instead of have := ...
in foo
Mario Carneiro (May 30 2023 at 09:30):
having this
be poisoned as a variable name in the rest of foo
sounds like a bigger issue
Sebastian Ullrich (May 30 2023 at 09:31):
Mario Carneiro said:
that would also not fix the example you just showed, since
this2
infoo
is a regular identifier and would get hygiened separately
It depends on whether you strip off macro scopes before checking whether a reference has a corresponding unhygienic
binding, which I assumed we would do
Sebastian Ullrich (May 30 2023 at 09:31):
I believe this would also be the approach closest to how anaphoric macros work in Scheme languages
Mario Carneiro (May 30 2023 at 09:33):
I guess I don't know the name resolution mechanism you have in mind then
Sebastian Ullrich (May 30 2023 at 09:40):
Any identifier syntactically equal to X
, whether it was later applied macro scopes or not, would bind to X._@._unhygienic
if it exists. Introducing such a binding would effectively deactivate hygiene for that identifier
Mario Carneiro (May 30 2023 at 09:44):
so if you have fun (X : A) (X._@._unhygienic : B) (X._@.Foo.0 : C) => ...
... => X
resolves toA -> B -> C -> B
... => X._@._unhygienic
resolves toA -> B -> C -> B
... => X._@.Foo.0
resolves toA -> B -> C -> C
... => X._@.Foo.1
resolves toA -> B -> C -> B
Sebastian Ullrich (May 30 2023 at 09:50):
Probably, yeah. Not that we have a use case for using unhygienic
on references like in the second case yet
Mario Carneiro (May 30 2023 at 09:50):
true
Mario Carneiro (May 30 2023 at 09:51):
it can probably come up though when uses are both a binding and a use
Sebastian Ullrich (May 30 2023 at 09:56):
Which in itself is rare :)
Mario Carneiro (May 30 2023 at 09:57):
it does still break things like
macro "have2" e:term : tactic =>
`(tactic| have $(mkIdent `this):ident := $e)
macro "foo" e:term : tactic =>
`(tactic| (have2 (); exact $e))
example (this : Nat) : Nat := by foo this
if we wanted the exact $e
use to skip the have2 1
binding
Mario Carneiro (May 30 2023 at 10:01):
Last time we talked about this I recall us discussing a way to capture just the macro scope without parsing an identifier. That is sufficient to cover have this :=
style tactics without any need for unhygienic idents
Mario Carneiro (May 30 2023 at 10:02):
e.g.
macro "have2" sc:macroScope e:term : tactic =>
`(tactic| have $(sc.mkIdent `this):ident := $e)
Sebastian Ullrich (May 30 2023 at 10:12):
Right, this might even be the most natural one to implement. Nothing more fundamental than the quotation elaborator would have to be changed
Sebastian Ullrich (May 30 2023 at 10:15):
So effectively we would inject this
into the calling lexical scope, not all scopes
Mario Carneiro (May 30 2023 at 11:07):
boom, this is working error free:
macro "have2" sc:macroScope e:term : tactic =>
`(tactic| have $(sc.mkIdent `this2):ident := $e)
example : Nat := by have2 1; exact this2
macro "foo" : tactic =>
`(tactic| (have2 1; exact this2))
example : Nat := by foo
macro "bar" e:term : tactic =>
`(tactic| (have2 (); exact $e))
example (this2 : Nat) : Nat := by bar this2
Mario Carneiro (May 30 2023 at 11:11):
the way it works is that the macroScope
parser just generates mkNode `macroScope #[mkIdent .anonymous]
, more or less. Amusingly, the quotation parser didn't even need to be changed, since it sees the ident node and marks it up just like any other
Sebastian Ullrich (May 30 2023 at 11:25):
I think you'll get into trouble with Syntax.reprint
this way (and the pretty printer, but that one we could of course override), that's why I wanted to avoid the ident
Sebastian Ullrich (May 30 2023 at 11:25):
But perhaps changing reprint
is the path of least resistance
Mario Carneiro (May 30 2023 at 11:31):
the pretty printer is fine, the formatter and parenthesizer implementations for the macroScope
parser are the same as for pushNone
Mario Carneiro (May 30 2023 at 11:31):
for reprint
I think it's also fine, because the ident has empty leading & trailing info and also has an empty rawVal
Mario Carneiro (May 30 2023 at 11:35):
I'm not exactly sure what happens if you use a syntax pattern match and ignore the macroScope arg though
Mario Carneiro (May 30 2023 at 11:38):
experimentally, it looks like it matches the macroScope against .anonymous
after erasing macro scopes, which is always true so I guess this is fine
Sebastian Ullrich (May 30 2023 at 12:38):
Oh yes, I somehow didn't think of rawVal
. Sounds good.
Mario Carneiro (Jun 02 2023 at 14:24):
Now that lean4#2246 is merged, this
is once more a variable name, and we can finally remove the mathlib hacks and uses of «this»
Last updated: Dec 20 2023 at 11:08 UTC