I am trying to achieve the equivalent of intro
in Ltac2, and I am failing. Here is one of my tries:
Std.intro None None;
let x :=
lazy_match! reverse goal with
| [ h : _ |- _ ] => h
end in
...
I would expect x
to contain an identifier that I could then use with &x
. It does indeed contain an identifier from the context, but it is a random one. How can I get the one produced by Std.intro
?
x
should contain the identifier, but beware that &x
doesn't do what you believe it does. With this syntax it just looks for the hypothesis named with the literal x
, not the bound variable x
.
Use Control.hyp
to do that.
Alternatively, why don't you provide an identifier to intro
? You could generate it with the Fresh module.
As I said in the other thread, I have a hard time recovering a useful ident.
Anyway, the documentation says: "&x
as a Coq constr expression expands to ltac2:(Control.refine (fun () => hyp @x))
." So, isn't that exactly your suggestion? What am I missing?
@x
means the literal ident x
Oh!
(we should probably have a shorter syntax for Control.hyp but that's another story that opens bikeshedding mode)
So, if I understand correctly, I should never use &
and @
in Ltac2 scripts, should I?
Why not?
You never use "foo"
in ML?
But how would I guess the actual literal name of a binder?
you mean the stuff introduced by intros? tough luck on that one
instead, you should 1. generate a fresh ident, 2. introduce it and 3. return it as a value
like the infamously broken intro x; ... [some stuff mentioning x]
in Ltac
that is, define a tactic fresh_intro : unit -> ident
that you can use this way
So, yes, that is my point, I never have an identifier at hand I can use with &
and @
, unless I am missing something.
it happens sometimes that the identifier is guaranteed to be preserved
e.g. refine (fun x => ...)
guarantees that the last binder is going to be x
but yes, &x
should mostly be reserved for inline proofs
as for @
that's another story, if you want to generate a fresh name with a given prefix obviously you'll use that
at least Ltac2 makes a difference between these concepts, and doesn't try at runtime to guess what you meant
By the way, I am constantly writingltac2:(Control.refine (fun () => foo))
. Is there some other way? If not, I would gladly have some ltac2val:(foo)
to make it shorter.
from Ltac1 you mean? or from a constr?
From constr.
there is the $x
syntax
Sorry, I meant that foo
is a complex term. I was under the impression that $x
only applies to an identifier.
yes, in this case you should let-bind the term to an Ltac2 variable
how's that different from just foo
?
like let x := '(my complex term) in ... ($x) ...
(assuming the term makes sense in the toplevel context)
Unfortunately, I cannot. Because I am usually in a context where I cannot bind an Ltac2 variable, e.g., constr:(forall x, ltac2:(Control.refine (fun () => something that needs to see x)))
.
you can always define your own notation, in this case
I am not sure that it's a great idea to write this kind of terms with quotations
it's currently lacking from the very barebone Ltac2 libraries but instead you should use some HOAS API
so, e.g. a function forall_ : constr -> (constr -> constr) -> constr
that one you can define it with quotations or low-level functions
by the way, this is one case where you're guaranteed statically that x
will keep its name
inside a quotation, for any identifier the last introduced binder with this name is guaranteed to be preserved
I am not sure HOAS would help here. The thing that needs to see x
in the context are evars. So, I am not quite sure how I would even write the constr -> constr
part of forall_
.
As I said in the other thread, I ended up writing a function evar
that produces an evar with a given type. And this evar is only meaningful in a given context. So, I have things like:
Control.refine (fun _ => open_constr:(fun x : $t => _ : ltac2:(Control.refine (fun () => evar 'Prop))))
not very pretty, please open PRs if you want to get closer to a 'batteries included' state
Pierre-Marie Pédrot said:
e.g.
refine (fun x => ...)
guarantees that the last binder is going to bex
So, I just tried it, and unfortunately, it does not seem to be true. If I do the following:
Control.refine (fun () => open_constr:(fun x : nat => _ : (_ : Prop))) ;
Control.focus 1 1 (fun () =>
Message.print (Message.of_constr (Control.goal ())) ;
let y := constr:(eq &x) in
Control.refine (fun () => open_constr:(fun x : nat => _ : (_ : Prop))) ;
Control.focus 1 1 (fun () =>
Message.print (Message.of_constr (Control.goal ())) ;
let y := constr:($y &x) in
Message.print (Message.of_constr y))).
the final term being printed is x = x
, while I would expect x0 = x
. (The intermediate print
s are just there to double-check that I am focusing the correct subgoals.)
So, I cannot rely on refine (fun x => ...)
guaranteeing anything about the name of the last binder.
Am I missing something?
that one is another issue (or call it a design choice if you want) which is that there has been a variable capture on an open var
I didn't claim anything about that situation, btw
my claim is about referring to the dynamic environment from within a quotation
here you built a term eq x
that refers to a context variable x
but then you pull the rug under the feet of that term because you change the dynamic environment to another one
it's your responsibility to check that the environment you're using the term in makes sense
(you could get into way worse situations, e.g. clearing the variable or changing its type)
That's another argument for HOAS, also, because it prevents this kind of situation (the renaming is implicit)
Pierre-Marie Pédrot said:
here you built a term
eq x
that refers to a context variablex
but then you pull the rug under the feet of that term because you change the dynamic environment to another one
I don't understand this part. The term eq x
is already created. I am not trying to create it after the second refine.
Yes, it's already created and it's just eq x
where x
is a name
after the second refine, the last introduced binder is also x
so you get eq x x
the previous x
was renamed into x0
in the dynamic context but we don't do anything for Ltac2 values
(and in general, we wouldn't be able to)
if instead of binding this term as an Ltac2 value, you had, say, pose
d it, you'd indeed see eq x0 x
So, I am back to step one. I cannot trust &x
. I have to manually create a fresh identifier.
what do you want to do exactly?
the original problem you raised at the beginning of the thread is solved IIUC
It is not, because your suggestion of using Control.hyp x
suffers from the same shadowing issue. So, I cannot use that.
?
you want to intro a variable
you intro it, you look at its name, poof
So, I just tried it, and unfortunately, it does not seem to be true. If I do the following:
ah it's wellknown: ltac variable bypasses typechecking again
but with ltac2 instead
Ltac2 is no more evolved than Ltac1 in that regard
it inherits the same low-level code
Pierre-Marie Pédrot said:
you intro it, you look at its name, poof
But its name is always x
. So, whichever term I create using it sooner or later becomes invalid.
well, if you escape the context yes
if you do this in a well-scoped way, without any clear and generating a fresh ident at every intro you'll be fine
whichever term I create using it sooner or later becomes invalid.
terms within a context do have a shelf life, and that's this context
Last updated: Sep 09 2024 at 05:02 UTC