Zulip Chat Archive
Stream: lean4
Topic: custom incremental elaboration
Jovan Gerbscheid (Mar 20 2025 at 19:02):
I have some alternative syntax with alternative tactics, in the form
theorem .. := by
my_tactics
my_tac1
my_tac2
my_tac3
And I would like to be able to use incremental elaboration on this, just like how a normal lean tactic sequence has. Can someone give a minimal example of how to do this? I know Verso does this kind of thing, but for the whole file instead of a tactic sequence.
(@Mirek Olšák was also curious about this)
Mirek Olšák (Mar 20 2025 at 19:18):
I experimentally checked that incremental
tag together with evalTacticSeq
evaluates the tactics one by one but it doesn't save the intermediate state, as other scoped tactics do. So when I have
-- testing tactic, maybe lean calls it somehow
elab "echo " s:str : tactic => logInfo s
-- my_tactics scope
syntax (name := my_tactics) "my_tactics" ppLine tacticSeq : tactic
@[tactic my_tactics, incremental]
def myScopeElab : Tactic
| `(tactic|my_tactics $tactics) => do
evalTacticSeq tactics
| _ => throwUnsupportedSyntax
example : True := by
my_tactics
sleep 1000
echo "hello"
sleep 1000
echo "world"
trivial
and change the last world
, the entire sequence gets executed again. (but it doesn't in e.g. dotted scope)
Sebastian Ullrich (Mar 21 2025 at 09:29):
@[tactic my_tactics, incremental]
def myScopeElab : Tactic := Term.withNarrowedArgTacticReuse 1 evalTacticSeq
Not sure about the progress bar right now. Unfortunately all of this is quite finnicky, which is also why there are no simple docs for it.
Mirek Olšák (Mar 21 2025 at 19:30):
Thanks, however, as I was looking into it, I am still somewhat confused. We would like to do this with another state, possibly with a different monad which Term.withNarrowedArgTacticReuse
seems to support. But I couldn't figure out what is going on inside evalTacticSeq
. By looking into the code, evalTacticSeq
is defined to call evalTactic
which looks at the root of the syntax, finds tacticSeq
, and should call evalTacticSeq
back. Where is the code that actually process the tactics one by one, which is compatible with this incrementality?
Mirek Olšák (Mar 21 2025 at 20:14):
In particular, could I write something instead of
@[tactic my_scope, incremental]
def myScopeElab : Tactic :=
Term.withNarrowedArgTacticReuse 1
(fun stx =>
match stx with
| `(tacticSeq| $[$tacs]*) => do
tacs.forM (fun tac => evalTactic tac)
| _ => throwUnsupportedSyntax
)
to keep the incremental evaluation but keep control over the loop?
Sebastian Ullrich (Mar 22 2025 at 12:06):
Ah, you want to see the really gnarly details? They are in evalSepTactics
, good luck!
Mirek Olšák (Mar 22 2025 at 13:40):
Oh, so
@[tactic my_tactics, incremental]
def myScopeElab : Tactic := fun stx => evalSepTactics stx[1]
actually also works, right? Or is there a catch that needs the Term.withNarrowedArgTacticReuse 1
in front of it?
Mirek Olšák (Mar 22 2025 at 13:49):
thanks, I will try :-)
Mirek Olšák (Mar 22 2025 at 20:14):
The snapshots are currently stored in the Term.Context.tacSnap?
, and when I naively tried to store them in a monad on top of the stack, it didn't work. Do you think it would be possible in principle to store the snapshots above (so it can support any state), or would I have to modify the Term.Context
?
Mirek Olšák (Mar 23 2025 at 02:52):
I think have found a way to do it -- initialize
a custom Extension
, use it in my custom tactic evaluation, and make a local copy of evalSepTactics
, where I replace every evalTactic
with my custom evaluation using that Extension, like here: https://github.com/Human-Oriented-ATP/lean-humanproof/blob/main/HumanProof/Incremental.lean
Mirek Olšák (Mar 23 2025 at 03:18):
@Sebastian Ullrich , do you think it could be reasonable to make the evalSepTactics
more general in the standard library, to allow a custom evalTactic
? Besides the "human-proof" (slightly alternative proof environment we are now looking at), there can be also environments displaying the goal in a custom form, or running a custom simplification every step...
Sebastian Ullrich (Mar 23 2025 at 08:49):
Your myEvalTactic
doesn't actually do anything that couldn't be done in regular tactic elaborators?
Mirek Olšák (Mar 23 2025 at 08:55):
True but it is just a demo. The real use case we are looking at does indeed something substantially different from just extending the tactic syntax by a few more tactics.
Sebastian Ullrich (Mar 23 2025 at 09:23):
I don't see the problem with copying it out. This code is unlikely to change (i.e. break) in core, while your requirements may change further.
Mirek Olšák (Mar 23 2025 at 09:41):
I think that having a custom tactic evaluation inside evalSepTactic
(possibly with environment extension keeping the extra state) covers all possible use cases, so I don't really see how our requirements could change further. But sure, let us first try with a copy to check whether this extended evalSepTactic
can be indeed used for what we want, and we will get back to you later.
Mirek Olšák (Mar 24 2025 at 22:17):
I implemented the "real" thing with custom incremental elaboration -- our specific proof environment. I just wanted to say that it indeed didn't require anything more than a custom environment extension, and a custom command to evaluate a tactic inside the sequence (instead of the default `evalTactic). So I think that generalizing it in the library doesn't cost much, and could benefit more users in the future, who would like a custom proof environment. Having a custom script to run a line in the block is quite powerful for a tactic-writer. The only missing part (which we didn't need but some could find it helpful) would be detecting where the scope ends.
I would be also curious if there is a better way of writing:
Term.withNarrowedArgTacticReuse 1 (
Term.withNarrowedArgTacticReuse 0 (
Term.withNarrowedArgTacticReuse 0 (
(customEvalSepTactics myEvaluationOfOneTacticStep)
)
)
) stx
but when it works, it works, I guess :-).
Should we make the PR for customEvalSepTactics
?
Jovan Gerbscheid (Mar 27 2025 at 18:39):
Last updated: May 02 2025 at 03:31 UTC