Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: Intended way to add local declaration to local context


Bhavik Mehta (Apr 08 2025 at 02:41):

In my tactic, I'd like to add a have statement, which in my understanding means adding something to the LocalContext. Poking around that API, I see docs#Lean.LocalContext.addDecl, but this isn't meant to be used directly. I also see that I can do evalTactic (← (tactic| have ...))`, but this feels too high-level to be the right solution. What's the intended API / method for this?

Mario Carneiro (Apr 08 2025 at 02:43):

docs#Lean.MVarId.intro1P probably

Mario Carneiro (Apr 08 2025 at 02:44):

see also docs#Mathlib.Tactic.haveLetCore

Thomas Murrills (Apr 08 2025 at 02:44):

I think you might be looking for docs#Lean.MVarId.assertHypotheses and friends!

Mario Carneiro (Apr 08 2025 at 02:44):

and docs#Lean.MVarId.assert

Bhavik Mehta (Apr 08 2025 at 02:45):

I don't think intro1P is what I'm looking for, but the other three suggestions look very helpful. What's the distinction between haveLetCore and assert(Hypotheses)?

Mario Carneiro (Apr 08 2025 at 02:45):

haveLetCore is halfway to a tactic frontend, notice that it takes Syntax arguments

Mario Carneiro (Apr 08 2025 at 02:46):

docs#Lean.MVarId.note is also basically have, using its ancient name

Bhavik Mehta (Apr 08 2025 at 02:47):

(deleted)

Thomas Murrills (Apr 08 2025 at 02:47):

Mario Carneiro said:

docs#Lean.MVarID.note is also basically have, using its ancient name

Ohh, I was wondering about that!

Mario Carneiro (Apr 08 2025 at 02:48):

once upon a time note was the tactic and have was the term. We're talking lean 3.1 or so

Bhavik Mehta (Apr 08 2025 at 02:54):

note seems to do what I want, thank you! (Although it's slightly confusing to me that I'm changing the mvar, and I don't know what the first argument it returns is...)

Mario Carneiro (Apr 08 2025 at 02:55):

most "MetaM tactics" take and return an MVarId

Mario Carneiro (Apr 08 2025 at 02:56):

that's basically your current goal state

Mario Carneiro (Apr 08 2025 at 02:56):

you thread it around explicitly, and the number of MVarIds you get back is the number of subgoals

Bhavik Mehta (Apr 08 2025 at 02:57):

Ah, including the local context. So the idea is that I'm meant to pass around this MVarId (presumably with the same name), and rewrite over the old ones? Isn't this exactly what a state monad is meant to handle for me?

Mario Carneiro (Apr 08 2025 at 02:57):

note returns an FVarId as well (which exists in the returned MVarId goal state), which refers to the hypothesis you've just created

Mario Carneiro (Apr 08 2025 at 02:57):

Bhavik Mehta said:

Ah, including the local context. So the idea is that I'm meant to pass around this MVarId (presumably with the same name), and rewrite over the old ones? Isn't this exactly what a state monad is meant to handle for me?

Yes, that state monad is called TacticM

Mario Carneiro (Apr 08 2025 at 02:59):

but what you lose with that monad is type soundness wrt subgoals as I mentioned. You can't tell the difference between a tactic that solves the goal and one which operates on two goals and produces three subgoals, they all have the same type

Bhavik Mehta (Apr 08 2025 at 02:59):

Right okay, this is why the input type of docs#Lean.Elab.Tactic.liftMetaFinishingTactic is what it is

Mario Carneiro (Apr 08 2025 at 02:59):

and you are never really sure whether you even have a main goal

Mario Carneiro (Apr 08 2025 at 03:00):

which impacts how type checking works since every expression has to elaborate in the context of the appropriate goal

Bhavik Mehta (Apr 08 2025 at 03:01):

I think I see, so the state monad I wanted does exist but it's more precise in some sense to handle these things by hand

Mario Carneiro (Apr 08 2025 at 03:03):

same thing with FVarIds, the local context is stored in the state but you want to hold on to FVarIds as "handles" to them so that you don't get confused and have to go find them again by name and worry about shadowing and not finding the right one later etc

Bhavik Mehta (Apr 08 2025 at 03:04):

Just to be clear, the local context is stored in the state of MetaM (which I presume is some ReaderT/StateT/IO stack), or in the state of TacticM? edit, just found the answer to this in the metaprogramming book, it's in the state of MetaM

Mario Carneiro (Apr 08 2025 at 03:04):

The local context is in the read only state of MetaM

Mario Carneiro (Apr 08 2025 at 03:05):

this is why anything that changes the local context will be in a callback shape

Bhavik Mehta (Apr 08 2025 at 03:06):

Am I to read that as docs#ReaderT.adapt, or something else? I don't quite see the callback shape in docs#Lean.MVarId.note, unless it's the MVarId passing (and even then I don't really see how that's a callback shape)

Mario Carneiro (Apr 08 2025 at 03:07):

ah, good point

Mario Carneiro (Apr 08 2025 at 03:08):

I believe it actually isn't changing the local context (it can't), which means you probably have to do mvar.withContext immediately after the note call on the mvar you get back if you want to make use of the fvarid and generally do things in the new context

Mario Carneiro (Apr 08 2025 at 03:09):

and docs#Lean.MVarId.withContext is hopefully more obviously in a callback shape

Bhavik Mehta (Apr 08 2025 at 03:12):

I think this makes sense, thanks!


Last updated: May 02 2025 at 03:31 UTC