Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: simulating interactive tactics in metaprogramming


Xavier Martinet (Jan 10 2022 at 17:28):

Hi everyone,

I am trying to simulate a sequence of tactic.interactive.<tactic_name> within a standard meta def metaprogramming definition. In order to to so, I write :

g  tactic.mk_meta_var goal,
tactic.set_goals [g]

then

tactic.<tactic_name>`  -- instead of `tactic.interactive.<tactic_name>`

However, due to the specific implicit arguments of the tactic.interactive.<tactic_name> (parsed arguments), I have to unfold the definition. For instance, if I want to use norm_num, I would write :

f  norm_num.get_step,
tactic.norm_num f [] interactive.loc.wildcard

However this solution is not tractable for some tactics, such as linarith for instance: as tactic.linarith calls linarith.mk_single_comp_zero_pf whose definition contains a [norm_num] that is parsed as tactic.interactive.norm_num. But as we are not in interactive mode anymore, there is no implicit parsed arguments, so the call fails. The workaround I found is to copy-paste ALL functions called as to eventually change [norm_num] by do f ← norm_num.get_step, tactic.norm_num f [] interactive.loc.wildcard. This is obviously not super pratical nor scalable to other tactics.

Is there a way in metaprogramming to have constructions such as [<tactic_name>]` are correctly called with their implicit parsed arguments?

cc @Timothee Lacroix

Gabriel Ebner (Jan 10 2022 at 17:45):

You can call tactic.interactive.linarith directly, the parser arguments are just wrappers. For example, parse p? reduces to option ...:

#check tactic.interactive.linarith none (some ()) (some []) { split_ne := tt } -- tactic unit

Gabriel Ebner (Jan 10 2022 at 17:46):

(If you don't know what to write after some, you can at first write some _. Then Lean will tell you explicitly what type it expected there.)

Xavier Martinet (Jan 10 2022 at 18:11):

Thanks.

However for some reasons the issue remains: when there is something like [norm_num] written in a definition, it just fails unless I change it explicitely to tactic.interactive.norm_num none none

As tactic.interactive.linarith calls mk_single_comp_zero_pf which contains [norm_num,done], it does not do exactly the same thing as in interactive mode

Xavier Martinet (Jan 10 2022 at 18:19):

(sorry, I meant tactic.interactive.norm_num [] interactive.loc.wildcard)

Xavier Martinet (Jan 10 2022 at 18:33):

Actually even the cfg.discharger, which is equal by default to [norm_num], needs to be set to {discharger := tactic.interactive.ring none}

so a regular by linarith is actually a tactic.interactive.linarith none none none {discharger := tactic.interactive.ring none} instead of a tactic.interactive.linarith none none none

and then, as mentionned above, I still need to patch mk_single_comp_zero_pf for the same reason (it contains [norm_num,done])

Rob Lewis (Jan 10 2022 at 19:21):

To call a tactic literally as you would write it in interactive mode, you can use the notation `[linarith]. This is a term with type tactic unit. But you won't be able to pass variable arguments to it. That is, you can write `[linarith [h1, h2]], but h1 and h2 will refer to whatever is in the context at the time the tactic is called; if you have e : expr when you're defining your tactic, you can't write `[linarith [e]].

Xavier Martinet (Jan 10 2022 at 20:32):

Thanks!

But how are the implicit parsed arguments created? For instance:
1) in tactic.interactive.linarith, there is a cfg : linarith.linarith_config where by default cfg.discharger := [ring]. When tactic.interactive.ring is actually called, its parameter red : parse (tk "!")? is automatically filled with none.
2) in the case of [norm_num,done] in mk_single_comp_zero_pf, the same goes for the [norm_num] parameters: (hs : parse simp_arg_list) and (l : parse location)are respectively set to "empty/default" values hs := [] and l := interactive.loc.wildcard.

What I don't quite understand is: how are those arguments created? Is there some kind of "parsing context" in interactive mode that enable argument creation? If so, is there a way when in metaprogramming to fill out this context so in 1) cfg.discharger := [ring] is actually interpreted as cfg.discharger := tactic.interactive.ring none, and in 2) [norm_num,done] is interpreted as tactic.interactive.norm_num [] interactive.loc.wildcard >> tactic.interactive.done ?

Xavier Martinet (Jan 10 2022 at 20:37):

I assume I would have to fiddle with the lean.parser_state: any way to do it in Lean itself?

Rob Lewis (Jan 10 2022 at 20:52):

When you write ring in interactive mode, you're calling src#tactic.interactive.ring . The argument red has type interactive.parse (optional (lean.parser.tk "!")). If p : parser T, interactive.parse p is defeq to T, so this argument red really has type option unit. The interactive.parse tag is what makes the input syntax different in interactive mode: if it wants to parse optional (tk "!"), it will try to parse tk "!" and return some if it succeds, otherwise none.

Rob Lewis (Jan 10 2022 at 20:53):

I'm not sure what you mean by the rest of your question. `[ring] is the same as tactic.interactive.ring none. It runs interactive mode parsing inside the `[...] block.

Xavier Martinet (Jan 10 2022 at 21:09):

Thanks Rob. I need to process that as I am surely being confused between several notions

Xavier Martinet (Jan 11 2022 at 09:54):

Thanks a lot for your answers.

I have isolated the code that caused me troubles:

import tactic

meta def tactic.interactive.dummy : tactic unit := tactic.trace "DUMMY"

meta def apply_dummy_on_app: expr  tactic unit
| e@(expr.app _ _) := `[dummy] <|> tactic.trace "FAIL"
| e@(expr.lam _ _ _ _) := do _, body  tactic.open_n_lambdas e 1, apply_dummy_on_app body
| _ := return ()

meta def apply_dummy (decl_nm : name) : tactic unit := do
  env  tactic.get_env,
  decl  env.get decl_nm,
  apply_dummy_on_app decl.value

meta def apply_dummy_with_ref (decl_nm : name) : tactic unit := do
  tactic.using_new_ref (0 : ) $ λ dp_ref, apply_dummy decl_nm


theorem mythm : 0 = 0 := eq.refl _

run_cmd apply_dummy `mythm  -- SUCCESS
run_cmd apply_dummy_with_ref `mythm  -- FAILS

For a reason I cannot fathom, the fact that there is a tactic.using_new_ref makes the use of the [<tactic>] construction fail.
Do you see any explanations?

Gabriel Ebner (Jan 11 2022 at 10:04):

MWE:

run_cmd tactic.using_new_ref (0 : ) $ λ _, `[skip]
-- Failed to copy state to another thread

Gabriel Ebner (Jan 11 2022 at 10:05):

What's happening is that Lean records the tactic state in interactive tactics (yes, even the ones in `[] blocks) to show the goal in the editor.

Xavier Martinet (Jan 11 2022 at 10:18):

Thanks!!! And why does it fail to copy the tactic state? the "other" thread is the one where there is a specific value for the ref?

Gabriel Ebner (Jan 11 2022 at 10:35):

That's just because the case with references wasn't implemented. lean#666

Gabriel Ebner (Jan 11 2022 at 10:35):

References are just a tactic-state specific map from number->object.


Last updated: Dec 20 2023 at 11:08 UTC