Zulip Chat Archive
Stream: mathlib4
Topic: expand `have` capabilities
Arthur Paulino (Mar 16 2022 at 20:04):
I wanna try to implement unstructured have
but I will need some guidance.
I have this:
elab "have " id:ident " : " t:term " dont_wanna_do_this " p:term : tactic => do
let t ← elabTerm t none
let p ← elabTermEnsuringType p t
liftMetaTactic fun mvarId => do
let mvarIdNew ← assert mvarId id.getId t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [mvarIdNew]
example : true := by
have h : Nat dont_wanna_do_this 5
simp
Instead of providing a term of the given type, I want to turn that type into a new goal (and add it to the list of goals).
So I have two questions:
- How can I add
h : Nat
to the context without relying onassert
(nor on a given term ofNat
)? - How can I add
⊢ Nat
as a new goal?
Mario Carneiro (Mar 16 2022 at 20:05):
(aside: I object to the term "unstructured", since all uses of this tactic in mathlib are quite structured)
Mario Carneiro (Mar 16 2022 at 20:06):
it's a version of have
that keeps the proof inside the same tactic block
Arthur Paulino (Mar 16 2022 at 20:07):
Yeah I don't understand why "unstructured" either. I'm just mimicking what's on the Mathport/Syntax.lean
file
Mario Carneiro (Mar 16 2022 at 20:08):
I'm confused why you have the dont_wanna_do_this
part
Mario Carneiro (Mar 16 2022 at 20:08):
why aren't you just using the syntax as is?
Arthur Paulino (Mar 16 2022 at 20:10):
I just want to be explicit that I don't want to rely on p
. With my current knowledge, the only way that I know how to add h : Nat
to the context is through assert
. And it relies on a term of the given type
Mario Carneiro (Mar 16 2022 at 20:10):
You can use assert
, you just create a new metavariable with the desired type and pass it to assert
and also put it in the goal list
Arthur Paulino (Mar 16 2022 at 20:12):
/--
Convert the given goal `Ctx |- target` into `Ctx |- type -> target`.
It assumes `val` has type `type` -/
def assert (mvarId : MVarId) (name : Name) (type : Expr) (val : Expr) : MetaM MVarId
How can I create an Expr
of type type
at this point? Isn't this the whole theorem proving challenge?
Arthur Paulino (Mar 16 2022 at 20:12):
(I'm talking about the 4th argument val
)
Arthur Paulino (Mar 16 2022 at 20:13):
I would appreciate a hint without much spoiling, if possible :)
Arthur Paulino (Mar 16 2022 at 20:17):
Huh, wait, something clicked in my head. Let me try something
Arthur Paulino (Mar 16 2022 at 21:05):
Alright, I think I got it:
elab "have " id:ident " : " t:term : tactic => do
let t ← elabTerm t none
let p ← mkFreshExprMVar t
liftMetaTactic fun mvarId => do
let mvarIdNew ← assert mvarId id.getId t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
let goals ← getUnsolvedGoals
example : Nat := by
have h : Nat
· exact 5
exact h
Please let me know if this is too naive (of course I still need to generalize an optional identifier if the user wants to store on this
)
Arthur Paulino (Mar 16 2022 at 21:44):
I don't see a clear advantage of this except for being able to change the goals order as in:
example : Nat := by
have h : Nat
swap
exact h
exact 5
Are there other benefits? I thought about being able to add tags to the subgoals. Maybe that can be useful to make proofs more organized with case <tag> => tacSeq
?
Mario Carneiro (Mar 16 2022 at 21:45):
you want the first two lines to be in the liftMetaTactic
block, that also sets the context
Mario Carneiro (Mar 16 2022 at 21:45):
the version that changes goals order is already a thing, that's suffices
Mario Carneiro (Mar 16 2022 at 21:46):
you should name the subgoals as well, I think it makes sense for the mvar to be named the same as the identifier
Arthur Paulino (Mar 16 2022 at 21:47):
Mario Carneiro said:
you want the first two lines to be in the
liftMetaTactic
block, that also sets the context
I can do this:
elab "have " id:ident " : " t:term : tactic => do
let t ← elabTerm t none
liftMetaTactic fun mvarId => do
let p ← mkFreshExprMVar t
let mvarIdNew ← assert mvarId id.getId t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
But elabTerm
can't be executed in MetaM
. Unless I'm doing something wrong
Mario Carneiro (Mar 16 2022 at 21:47):
At least in lean 3, opening another tactic block via have : foo := by { }
instead of have : foo, { }
meant that the two blocks could not share metavariables - everything has to be solved in the nested block
Arthur Paulino (Mar 16 2022 at 21:50):
This is already happening:
example : Nat := by
have h : Nat
· let q := 5
exact 5
let a := q -- unknown identifier 'q'
exact h
Mario Carneiro (Mar 16 2022 at 21:51):
what are you showing?
Mario Carneiro (Mar 16 2022 at 21:52):
I'm not sure if withMainContext
needs to be called before the elabTerm
; to test that you should try referencing a variable in another goal..?
Mario Carneiro (Mar 16 2022 at 21:54):
but also, you should be using the original syntax with haveIdLhs
instead of "have " ident " : " term
Mario Carneiro (Mar 16 2022 at 21:54):
those produce different syntax trees
Arthur Paulino (Mar 16 2022 at 21:58):
Mario Carneiro said:
I'm not sure if
withMainContext
needs to be called before theelabTerm
; to test that you should try referencing a variable in another goal..?
The variables don't seem to be shared among goals:
example : true ∧ true := by
constructor
have h : Nat
have w : String
/-
⊢ String
w : String
⊢ ℕ
case left
h : ℕ
⊢ true = true
case right
⊢ true = true
-/
simp
simp
Arthur Paulino (Mar 16 2022 at 22:03):
I think I am misunderstanding what you're saying
Mario Carneiro (Mar 16 2022 at 22:15):
The question is whether the elaboration of the type is occurring in the right context, because tactics operate on a bunch of goals and different goals have different variables in them and hence elaborate terms differently
Arthur Paulino (Mar 16 2022 at 22:19):
Hmm, even with withMainContext
it's still possible to do:
elab "have " id:ident " : " t:term : tactic => do
withMainContext do
let t ← elabTerm t none
let name := id.getId
liftMetaTactic fun mvarId => do
let p ← mkFreshExprMVar (userName := name) t
let mvarIdNew ← assert mvarId name t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
example : true ∧ true := by
constructor
have h1 : String
have h2 : String
case h1 =>
exact h2 -- this works, but is it desired?
case h2 =>
exact ""
simp
simp
Arthur Paulino (Mar 16 2022 at 22:27):
I will upgrade the syntax to haveIdLhs
after I get this POC working nicely
Mario Carneiro (Mar 16 2022 at 22:52):
That all seems to be working as intended
Mario Carneiro (Mar 16 2022 at 22:53):
withMainContext
doesn't seem to make a difference
Mario Carneiro (Mar 16 2022 at 22:53):
the question is about free variables in t
and where they come from
Mario Carneiro (Mar 16 2022 at 22:53):
so you need an example with a dependent type instead of just String
Mario Carneiro (Mar 16 2022 at 22:55):
like this
import Lean
open Lean Meta Elab Tactic
elab "have " id:ident " : " t:term : tactic => do
-- withMainContext do
let t ← elabTerm t none
let name := id.getId
liftMetaTactic fun mvarId => do
let p ← mkFreshExprMVar (userName := name) t
let mvarIdNew ← assert mvarId name t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
example : True := by
have h1 : String := sorry
have h2 : h1 = h1 -- fail
Arthur Paulino (Mar 17 2022 at 00:59):
More progress:
syntax "have " (ident)? " this "? " : " term (" as " ident)? : tactic
private def haveDef (name : Name) (t : Syntax) (i? : Option Syntax) : TacticM Unit :=
withMainContext do
let t ← elabTerm t none
let caseName := match i? with
| none => name
| some i => i.getId
liftMetaTactic fun mvarId => do
let p ← mkFreshExprMVar (userName := caseName) t
let mvarIdNew ← assert mvarId name t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
elab_rules : tactic
| `(tactic|have $h:ident : $t:term $[as $i:ident]?) => haveDef h.getId t i
| `(tactic|have $[this]? : $t:term $[as $i:ident]?) => haveDef `this t i
example {a b : Nat} (h : a = b) : 2 * a + b = a + (2 * b) := by
rw [h]
have : 2 * b = b + b as h_two_mul
on_goal -1 => rw [this, Nat.add_assoc] -- targeting the final goal
case h_two_mul => ring
With this syntax it's possible to name the subgoals with as <name>
Mario Carneiro (Mar 17 2022 at 01:01):
:thinking: Seems like have h this : foo
is legal syntax
Mario Carneiro (Mar 17 2022 at 01:02):
I don't think the as
thing is necessary, people won't be using the case name in most cases and the given identifier seems as good a name as any
Mario Carneiro (Mar 17 2022 at 01:02):
not to mention that it's not entirely clear if it will play well with the haveIdLhs
parser
Arthur Paulino (Mar 17 2022 at 01:03):
Btw I'm having some troubles with the haveIdLhs
parser
Mario Carneiro (Mar 17 2022 at 01:06):
care to share?
Arthur Paulino (Mar 17 2022 at 01:09):
Apparently it's not allowed to say have this : Nat
with it
Mario Carneiro (Mar 17 2022 at 01:10):
that's true, although it's not obvious we need to do anything about it
Mario Carneiro (Mar 17 2022 at 01:10):
you can just write have :
instead
Mario Carneiro (Mar 17 2022 at 01:11):
If you want to make this
acceptable in that position then probably it should be a lean core change
Mario Carneiro (Mar 17 2022 at 01:13):
I'm not really sure why this
became a keyword in the first place, besides perhaps some tactic that forgot to use &"this"
instead of "this"
in the syntax. @Sebastian Ullrich do you know?
Arthur Paulino (Mar 17 2022 at 01:16):
Alright, this works:
syntax "have " Parser.Term.haveIdLhs : tactic
private def haveDef (name : Name) (t : Syntax) : TacticM Unit :=
withMainContext do
let t ← elabTerm t none
liftMetaTactic fun mvarId => do
let p ← mkFreshExprMVar (userName := name) t
let mvarIdNew ← assert mvarId name t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
elab_rules : tactic
| `(tactic|have $[$h?:ident]? : $t:term) =>
match h? with
| none => haveDef `this t
| some h => haveDef h.getId t
example {a b : Nat} (h : a = b) : 2 * a + b = a + (2 * b) := by
rw [h]
have h' : 2 * b = b + b
on_goal -1 => rw [h', Nat.add_assoc] -- targeting the final goal
case h' => ring
Is that elab_rule
enough?
Mario Carneiro (Mar 17 2022 at 01:16):
you aren't covering the have $[$h?:ident]?
case
Arthur Paulino (Mar 17 2022 at 01:18):
Without expliciting a type?
Mario Carneiro (Mar 17 2022 at 01:18):
right
Arthur Paulino (Mar 17 2022 at 01:18):
What is supposed to happen in this case?
Mario Carneiro (Mar 17 2022 at 01:19):
the type is a metavariable (not added as a goal)
Mario Carneiro (Mar 17 2022 at 01:21):
example : true :=
begin
have h,
-- 2 goals
-- ⊢ ?m_1
-- h : ?m_1
-- ⊢ true
end
Mario Carneiro (Mar 17 2022 at 01:21):
the lean 4 syntax also allows binders between the identifier and the colon, although it is lower priority since lean 3 doesn't support this
Arthur Paulino (Mar 17 2022 at 02:33):
How come it's not added as a goal? I see "2 goals" in your example after have h
Arthur Paulino (Mar 17 2022 at 03:23):
This seems to work, but for some reason I'm being required to put a semicolon after have
:
https://github.com/leanprover-community/mathlib4/pull/232
Help is appreciated :pray:
Mario Carneiro (Mar 17 2022 at 03:25):
FYI you can match both cases of have at once using | `(tactic|have $[$n:ident]? $[: $t:term]?) => haveDef n t
Arthur Paulino (Mar 17 2022 at 03:26):
I tried that, but it gave me an error
Arthur Paulino (Mar 17 2022 at 03:27):
I think it's related to the semicolon issue. The syntax tree might have some issue
Arthur Paulino (Mar 17 2022 at 03:28):
We might need a way to say "no line break allowed". Something similar to noWs
, which forbids spaces
Mario Carneiro (Mar 17 2022 at 03:30):
Most parsers use colGt
when parsing trailing idents for exactly this reason. This fixes the issue:
import Lean
def Lean.Parser.Term.haveIdLhs' :=
optional (ident >> many (ppSpace >>
checkColGt "expected to be indented" >>
(simpleBinderWithoutType <|> bracketedBinder))) >> optType
namespace Mathlib.Tactic
open Lean Elab.Tactic Meta Parser.Term
syntax "have " haveIdLhs' : tactic
elab_rules : tactic
| `(tactic|have $[$n:ident]? $[: $t:term]?) =>
withMainContext do
let name : Name := match n with
| none => `this
| some n => n.getId
let t ← match t with
| none => mkFreshTypeMVar
| some t => elabTerm t none
liftMetaTactic fun mvarId => do
let p ← mkFreshExprMVar (userName := name) t
let mvarIdNew ← assert mvarId name t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
example : True := by
have h
exact 5
simp
Arthur Paulino (Mar 17 2022 at 03:34):
Hmm, I don't understand it yet but I'm glad it solved the issue!
Will adjust the PR tomorrow
Arthur Paulino (Mar 17 2022 at 03:35):
Thank you :D
Mario Carneiro (Mar 17 2022 at 03:35):
The problem is that
example : True := by
have h
simp
is interpreted as
example : True := by
have h (simp : _) : _
Mario Carneiro (Mar 17 2022 at 03:35):
and without the indentation fix,
example : True := by
have h
simp
is also interpreted that way
Arthur Paulino (Mar 17 2022 at 03:37):
Can't we just forbid linebreaks after haveIdLhs
?
Arthur Paulino (Mar 17 2022 at 03:38):
(is it even possible to do so?)
Arthur Paulino (Mar 17 2022 at 03:38):
This way we wouldn't need to redefine the whole syntax
Mario Carneiro (Mar 17 2022 at 04:01):
well, this is arguably an issue in core
Mario Carneiro (Mar 17 2022 at 04:01):
it's also not a good idea to forbid linebreaks generally because not all uses fit on a line
Sebastian Ullrich (Mar 17 2022 at 09:20):
Mario Carneiro said:
I'm not really sure why
this
became a keyword in the first place, besides perhaps some tactic that forgot to use&"this"
instead of"this"
in the syntax. Sebastian Ullrich do you know?
I don't think you can use &
in leading position. But it also looks like a bad idea, since it would make it easy to introduce a variable this
that then can't be referenced.
Mario Carneiro (Mar 17 2022 at 15:14):
@Sebastian Ullrich I can't understand your comment. Why is this
not like any other ident name? You should be able to just use it like this + 2
.
Sebastian Ullrich (Mar 17 2022 at 15:15):
https://github.com/leanprover/lean4/issues/821
Sebastian Ullrich (Mar 17 2022 at 15:25):
In short, because this
bindings are not regular, it cannot be a regular identifier
Arthur Paulino (Mar 17 2022 at 15:37):
I remember times in which the well-behaved metavariable names helped me make sense of what was going on:
example : true :=
begin
have h,
-- 2 goals
-- ⊢ ?m_1
-- h : ?m_1
-- ⊢ true
end
But with this implementation in Lean 4, they are named after some "arbitrarily" high number:
example : True := by
have h
/-
case h
⊢ ?m.2156
h : ?m.2156
⊢ True
-/
Should I worry about this?
Mario Carneiro (Mar 17 2022 at 15:40):
Sebastian Ullrich said:
In short, because
this
bindings are not regular, it cannot be a regular identifier
I don't really understand the hygiene system, but can't have := ...; this
be treated exactly like have this := ...; this
? That is, the have
introduces an invisible this
token suitably marked up with hygiene stuff so that the second this
resolves
Mario Carneiro (Mar 17 2022 at 15:42):
@Arthur Paulino No, that's a general issue with mvar printing not related to have
Sebastian Ullrich (Mar 17 2022 at 15:43):
Mario Carneiro said:
Sebastian Ullrich said:
In short, because
this
bindings are not regular, it cannot be a regular identifierI don't really understand the hygiene system, but can't
have := ...; this
be treated exactly likehave this := ...; this
? That is, thehave
introduces an invisiblethis
token suitably marked up with hygiene stuff so that the secondthis
resolves
Hygiene happens when the quotation is evaluated, i.e. when the syntax tree is constructed. The this
binding does not exist at that point in any form.
Mario Carneiro (Mar 17 2022 at 15:43):
I think there are other tactics that want to have this "invisible this
" behavior for other names too
Mario Carneiro (Mar 17 2022 at 15:44):
The have
exists though, can't it be marked up somehow?
Mario Carneiro (Mar 17 2022 at 15:44):
that is, we are syntactically at a point where we know it is have
and that it needs a this
Sebastian Ullrich (Mar 17 2022 at 15:46):
Well, this is a specific property of the Lean hygiene algorithm that is different in e.g. Racket: only identifiers are annotated with hygiene information, so we can't just say "introduce this
with the same scope as the have
keyword"
Sebastian Ullrich (Mar 17 2022 at 15:46):
I'm relatively happy with the this
design so far, it makes it completely clear that it does not follow the standard identifier scoping
Mario Carneiro (Mar 17 2022 at 15:46):
I'm not sure the original issue is all that important though. Is there a reason I should care if I don't write things like that in syntax quotations? i.e. as a regular user how does this affect me?
Mario Carneiro (Mar 17 2022 at 15:47):
let this := 1
is a syntax error. This makes me unhappy as a user
Mario Carneiro (Mar 17 2022 at 15:48):
and there are lots of other places that accept every ident but not this
Sebastian Ullrich (Mar 17 2022 at 15:49):
let this
is not a syntax error
Mario Carneiro (Mar 17 2022 at 15:50):
I thought it was, perhaps fun this => ...
?
Arthur Paulino (Mar 17 2022 at 15:51):
Mario Carneiro said:
let this := 1
is a syntax error. This makes me unhappy as a user
I had made it possible with a custom syntax for the replace
tactic: link
Mario Carneiro (Mar 17 2022 at 15:51):
#check fun this => 1
results in a weird error
Sebastian Ullrich (Mar 17 2022 at 15:52):
Hah, that should probably be a special case in the match
elaborator. We shouldn't even elaborate a match in this case.
Sebastian Ullrich (Mar 17 2022 at 15:54):
But going back to the question of why you should care about the fix: in Lean 3 you assume that you can lift arbitrary tactic snippets into a helper tactic, no? In Lean 4 that would involve a syntax quotation, so the fix makes sure this
still works in the helper.
Sebastian Ullrich (Mar 17 2022 at 15:55):
Now Lean 4 actually doesn't let you lift arbitrary snippets exactly because of hygiene, but without the fix, using this
even completely internal to the helper would not work.
Mario Carneiro (Mar 17 2022 at 15:55):
I find that using identifiers of any kinds in syntax quotations does the wrong thing and I always use $(mkIdent `foo)
Mario Carneiro (Mar 17 2022 at 15:55):
which probably makes me a bad person
Sebastian Ullrich (Mar 17 2022 at 15:57):
I guess you don't perceive them as separate scopes then. Which makes sense coming from Lean 3, but I'd hope that's not the natural assumption to make without that background.
Mario Carneiro (Mar 17 2022 at 15:58):
I don't really know what that means. I want a foo
in my expression
Mario Carneiro (Mar 17 2022 at 15:58):
and writing foo
doesn't give me that
Sebastian Ullrich (Mar 17 2022 at 15:58):
Such that it is visible to the outside?
Mario Carneiro (Mar 17 2022 at 15:58):
such that the identifier is called foo
Mario Carneiro (Mar 17 2022 at 16:00):
my model of identifiers is probably not hygienic, I just think of them as names and everything else scares me
Mario Carneiro (Mar 17 2022 at 16:00):
(I exaggerate, but hygienic identifiers are significantly more complex to deal with as a tactic author)
Mario Carneiro (Mar 17 2022 at 16:03):
I think that hygiene is most important for macro tactics and other "simple" expansions where you aren't doing anything except shuffling syntax around and using syntax quotations
Sebastian Ullrich (Mar 17 2022 at 16:03):
I can't say much without a specific example. But the whole point of hygiene is to make scoping in metaprograms behave like you would expect in regular programs. If a variable reference in a helper function doesn't bind to a variable of that name in the caller, then the same should be true for a helper tactic.
Mario Carneiro (Mar 17 2022 at 16:04):
I am usually writing elab
tactics where very little is done using syntax quotations, and there it seems that hygiene is only an interference
Mario Carneiro (Mar 17 2022 at 16:05):
An example might be Arthur's have
tactic above; what does it need to do to handle this
correctly?
Arthur Paulino (Mar 17 2022 at 16:07):
Mario Carneiro said:
An example might be Arthur's
have
tactic above; what does it need to do to handlethis
correctly?
This code did it. But it relied on that custom syntax
Mario Carneiro (Mar 17 2022 at 16:07):
Here's a proposal: an invisibleThis
parser that parses nothing and yields a hygienic this
ident
Mario Carneiro (Mar 17 2022 at 16:08):
then have
and friends can use "have" (ident <|> invisibleThis) ...
Mario Carneiro (Mar 17 2022 at 16:09):
(or better yet, invisible(`this)
since that way you can use other names too)
Mario Carneiro (Mar 17 2022 at 16:10):
That would make have := 1; this
and have this := 1; this
produce the exact same Syntax
Mario Carneiro (Mar 17 2022 at 16:11):
(which is not currently the case btw, the second one adds a match)
Arthur Paulino (Mar 17 2022 at 16:12):
implicit(`this)
also works as a name
Sebastian Ullrich (Mar 17 2022 at 16:20):
You can use (ident <|> Lean.termThis)
, though perhaps you had something different in mind
Mario Carneiro (Mar 17 2022 at 16:20):
I'm talking about making the binder special and making this
a regular identifier again
Mario Carneiro (Mar 17 2022 at 16:22):
The reason have := 1; this
doesn't work is because the second this
gets marked up with hygiene information local to the scope and the first one doesn't, because it doesn't exist. To fix this we can have a parser that inserts a this
token exactly as though the extra characters this
were present in the original string
Sebastian Ullrich (Mar 17 2022 at 16:24):
The one fundamental rule of the parser is that the token sequence in the returned syntax tree is identical to the input
Mario Carneiro (Mar 17 2022 at 16:25):
Okay, then it's not a proper token but instead a node Parser.implicit._0.metadata #[]
or something
Mario Carneiro (Mar 17 2022 at 16:25):
it's your choice where you want to stuff the metadata
Sebastian Ullrich (Mar 17 2022 at 16:36):
That would work for the parser, but we would have to adjust the hygiene algorithm. It sounds a bit messy.
Sebastian Ullrich (Mar 17 2022 at 16:53):
For what it's worth, it would be nice if the desired tactic could be implemented purely as a macro. This comes close, but the case with the missing type adds it as an additional goal.
syntax "have " (colGt term)? (" : " colGt term)? : tactic
macro_rules
| `(tactic| have $[$id:ident]?) => `(tactic| have $[$id:ident]? : ?_ := ?_)
| `(tactic| have : $h) => `(tactic| have : $h := ?_)
| `(tactic| have $e:term $[: $h]?) => `(tactic| have $e:term $[: $h]? := ?_)
Arthur Paulino (Mar 17 2022 at 17:02):
Sebastian Ullrich said:
but the case with the missing type adds it as an additional goal
How come? Even if I provide the term? I would like Lean to elaborate the type for me in that case
Sebastian Ullrich (Mar 17 2022 at 17:04):
No, only if it can't be inferred
Sebastian Ullrich (Mar 17 2022 at 17:05):
Wait, I thought the point of the tactic was that you don't provide the term :upside_down:
Arthur Paulino (Mar 17 2022 at 17:08):
This no longer works :thinking:
example : True := by
have h
exact 5 -- invalid universe level, ?u.2348 is not greater than 0
simp
Sebastian Ullrich (Mar 17 2022 at 17:13):
Ah, the goals are in the wrong order
Gabriel Ebner (Mar 17 2022 at 17:29):
(deleted)
Arthur Paulino (Mar 17 2022 at 18:30):
Let's bring the discussion here.
What should I do with the syntax?
Mario Carneiro (Mar 17 2022 at 18:32):
Do you know how to support the binders?
Arthur Paulino (Mar 17 2022 at 18:33):
I still don't know what binders are :sweat_smile:
Mario Carneiro (Mar 17 2022 at 18:33):
if you can support the entire tactic that would be ideal
Mario Carneiro (Mar 17 2022 at 18:33):
The idea is that have foo x := x + 1
should define a function like have foo := fun x => x + 1
Mario Carneiro (Mar 17 2022 at 18:35):
have foo x : x < x + 1
would produce a proof of foo : (x : Nat) -> x < x + 1
on one branch and a proof obligation x : nat |- x < x + 1
on the other (note that x
is already introduced)
Arthur Paulino (Mar 17 2022 at 18:36):
So If i have more than one identifier it will always define a function?
Arthur Paulino (Mar 17 2022 at 18:37):
(using the first identifier as the function name and the others as parameters)
Mario Carneiro (Mar 17 2022 at 18:37):
yes, the syntax is an ident followed by a list of x
or (x)
or (x : ty)
Arthur Paulino (Mar 17 2022 at 18:38):
1 ident => current behavior
2+ ident => function
Arthur Paulino (Mar 17 2022 at 18:38):
I can do that
Mario Carneiro (Mar 17 2022 at 18:38):
it's not a separate case, really
Mario Carneiro (Mar 17 2022 at 18:39):
the haveIdLhs
parser gives you the list of binders and the name separately
Arthur Paulino (Mar 17 2022 at 18:41):
have foo := x + 1
have foo x := x + 1
Are those the same case?
Mario Carneiro (Mar 17 2022 at 18:41):
yes, the first one has binders []
and the second has binders [x]
Mario Carneiro (Mar 17 2022 at 18:41):
and have foo x y
would have binders [x, y]
Mario Carneiro (Mar 17 2022 at 18:42):
and have (x : ty)
would have binders [(x : ty)]
and no ident (i.e. this
)
Mario Carneiro (Mar 17 2022 at 18:42):
the parser handles all this already
Arthur Paulino (Mar 17 2022 at 18:43):
I will try it later
Arthur Paulino (Mar 17 2022 at 18:43):
is there an example of these binders handling?
Mario Carneiro (Mar 17 2022 at 18:47):
intro
tactic, or let
Mario Carneiro (Mar 17 2022 at 18:48):
you might just be able to delegate to regular have
Arthur Paulino (Mar 18 2022 at 14:07):
delegating to regular have
has the same issue from Sebastian's macro
Arthur Paulino (Mar 18 2022 at 18:48):
@Mario Carneiro I'm not sure I fully understood what you meant in the lean4 stream. This is my current code:
import Lean
def Lean.Parser.Term.haveIdLhs' : Parser :=
optional (ident >> many (ppSpace >>
checkColGt "expected to be indented" >>
(simpleBinderWithoutType <|> bracketedBinder))) >> optType
def Lean.Parser.Term.letIdLhs' : Parser :=
ident >> notFollowedBy (checkNoWsBefore "" >> "[")
"space is required before instance '[...]' binders to distinguish them from array updates `let x[i] := e; ...`" >>
many (ppSpace >> checkColGt "expected to be indented" >>
(simpleBinderWithoutType <|> bracketedBinder)) >> optType
namespace Mathlib.Tactic
open Lean Elab.Tactic Meta
syntax "have " Parser.Term.haveIdLhs' : tactic
syntax "let " Parser.Term.letIdLhs' : tactic
private def addToContext (name : Name) (t : Expr) (keepTerm : Bool) : TacticM Unit :=
let declFn := if keepTerm then define else assert
liftMetaTactic fun mvarId => do
let p ← mkFreshExprMVar (userName := name) t
let mvarIdNew ← declFn mvarId name t p
let (_, mvarIdNew) ← intro1P mvarIdNew
return [p.mvarId!, mvarIdNew]
private def elabType (t : Option Syntax) : Elab.TermElabM Expr :=
match t with
| none => mkFreshTypeMVar
| some t => elabTerm t none
elab_rules : tactic
| `(tactic|have $[$n:ident]? $[: $t:term]?) => withMainContext do
let name := match n with
| none => `this
| some n => n.getId
addToContext name (← elabType t) false
| `(tactic|have $n:ident $bs* $[: $t:term]?) => withMainContext do
let e ← Elab.Term.elabBinders bs $
fun es => do mkForallFVars es (← elabType t)
addToContext n.getId e false
| `(tactic|let $n:ident $[: $t:term]?) => withMainContext do
addToContext n.getId (← elabType t) true
| `(tactic|let $n:ident $bs* $[: $t:term]?) => withMainContext do
let e ← Elab.Term.elabBinders bs $
fun es => do mkForallFVars es (← elabType t)
addToContext n.getId e true
Arthur Paulino (Mar 18 2022 at 18:49):
I'm tackling let
and have
in the same file because there's a lot of code that can be reused
Mario Carneiro (Mar 18 2022 at 18:50):
have f (x : Nat) : x < x + 1
should produce goals
case f
x : Nat
|- x < x + 1
f : (x : Nat) -> x < x + 1
|- goal
Mario Carneiro (Mar 18 2022 at 18:51):
I think that means that you have to inline addToContext
and move some things around
Arthur Paulino (Mar 18 2022 at 18:55):
Alright. Another question: is there another elab_rule I am missing?
Mario Carneiro (Mar 18 2022 at 19:03):
IIRC the behavior is different if you put more than one symtax node in the same elab_rules
Mario Carneiro (Mar 18 2022 at 19:03):
so I think you want have
and let
to be separate
Mario Carneiro (Mar 18 2022 at 19:05):
also I think you should be matching (tactic| have $[$n:ident $bs*]? $[: $t:term]?)
Mario Carneiro (Mar 18 2022 at 19:06):
which is the only case
Arthur Paulino (Mar 18 2022 at 19:07):
Nice, tyvm :+1:
Arthur Paulino (Mar 18 2022 at 22:18):
Mario Carneiro said:
have f (x : Nat) : x < x + 1
should produce goals
case f x : Nat |- x < x + 1 f : (x : Nat) -> x < x + 1 |- goal
I don't know how to make it possible. I thought about a hacky solution performing intro x
multiple times (depending on the number of binders and their types)
Arthur Paulino (Mar 18 2022 at 22:19):
Hmm, maybe not "hacky", but kinda inefficient
Arthur Paulino (Mar 18 2022 at 23:56):
Okay I think it turned out pretty good actually: mathlib4#232
Last updated: Dec 20 2023 at 11:08 UTC