Zulip Chat Archive

Stream: new members

Topic: Invoking tactics from another tactic


Isak Colboubrani (Feb 05 2024 at 23:06):

I am writing a tactic that will selectively apply certain existing tactics based on conditions.

Is the following the recommended syntax for invoking a tactic (in this case linarith)?

Lean.Elab.Tactic.evalTactic <|← `(tactic|linarith)

How do I make the tactic name dynamic? In other words, how do I execute tacticToExecute given let tacticToExecute := "some tactic"?

Alex J. Best (Feb 07 2024 at 13:32):

I'm not sure this is really a recommended way to go about it, probably you only have a fixed list of options, I'd go with something more like this

import Lean
open Lean Meta   Elab Tactic
def a (n : Nat) : MetaM <| TSyntax `tactic := do match n with
| 0 => return  `(tactic|assumption)
| 1 => return  `(tactic|simp)
| _ => unreachable!

elab "asd" : tactic => do
  let as := 1
  Lean.Elab.Tactic.evalTactic <|  a as

example : 1 = 1 := by
  asd

Alex J. Best (Feb 07 2024 at 13:33):

If you had made a #mwe with an example of your setup it would have been much quicker for me to write this answer btw

Alex J. Best (Feb 07 2024 at 13:34):

You could also use a string or whatever in place of a nat index here, but fundamentally when using these tactic syntax quotations Lean wants to check that the syntax you are producing corresponds to valid tactic calls, so full dynamism seems ill-advised

Isak Colboubrani (Feb 07 2024 at 23:58):

@Alex J. Best Thanks Alex! I've resolved the issue by employing both runParserCategory and evalTactic together:

          match Lean.Parser.runParserCategory ( Lean.MonadEnv.getEnv) `tactic tacticToRun with
          | Except.error e => throwError e
          | Except.ok stx => Lean.Elab.Tactic.evalTactic $  `(tactic| $(⟨stx⟩))

Does this appear to be correct?

Kyle Miller (Feb 08 2024 at 02:15):

Lean.Elab.Tactic.evalTactic $ ← `(tactic| $(⟨stx⟩)) can be simply Lean.Elab.Tactic.evalTactic stx.

Could we #xy this? There's almost always a better thing to do than parsing strings. For example, your tactic might have a tactic argument. There are many tactics that take a discharger argument, where you can write something like my_tactic (discharger := linarith), and linarith can be replaced with an arbitrarily complicated tactic script.

Isak Colboubrani (Feb 08 2024 at 14:50):

@Kyle Miller Thank you! Using Lean.Elab.Tactic.evalTactic stx is indeed much cleaner.

In my logic, I have arrays of strings containing tactics that I wish to execute if certain criteria are met, hence the need to convert strings into tactics (using evalTactic <Lean.Syntax> after getting the Lean.Syntax object via Lean.Parser.runParserCategory). Please let me know if there's a more appropriate method to accomplish this without parsing strings.

I'm required to supply Lean.Syntax objects to Lean.Elab.Tactic.evalTactic. I'm contemplating whether storing Lean syntax objects directly in the array, as opposed to strings, might be a better strategy. Would this approach be advisable? Furthermore, is there an alternative method that enables "compile-time" verification of tactic availability?

Patrick Massot (Feb 08 2024 at 15:12):

You should definitely try to handle Syntax objects as much as possible.

Kyle Miller (Feb 08 2024 at 16:42):

Could we #xy more? I'd like to understand what sort of tactic you're writing, and why it's natural for you to have arrays of tactic strings/syntax rather than some other arrangement.

(Is it an array because you want to execute sequences of tactics? A tactic can be a composite of tactics, so there's no need for an array.)

Kyle Miller (Feb 08 2024 at 16:43):

How dynamic do you need this to be? Is there anything wrong with an if/else chain that does an appropriate Lean.Elab.Tactic.evalTactic <|← `(tactic|linarith) in each case?

Isak Colboubrani (Feb 10 2024 at 10:08):

@Kyle Miller Here is a #mwe showing what I'm currently doing:

syntax (name := mytactic) "mytactic" : tactic
@[tactic mytactic] def _mytactic : Lean.Elab.Tactic.Tactic := fun _ =>
  let tactics_1 : Array String := #[
    "some tactic 1",
    "some tactic 2",
    "some tactic 3",
    "aesop"
  ]
  -- let tactics_2 : ...
  -- let tactics_3 : ...
  Lean.Elab.Tactic.withMainContext do
    -- some logic determining which of tactics_1, tactics_2, etc should be run
    tactics_1.forM (λ tactic => do
      -- some logic 1
      match Lean.Parser.runParserCategory ( Lean.MonadEnv.getEnv) `tactic tactic with
        | Except.error e => Lean.logInfo m!"Error '{tactic}': {e}"
        | Except.ok s => Lean.Elab.Tactic.evalTactic s
      -- some logic 2
    )
    -- some logic 3

example (h :  n, n = 0) :  n, n ^ n = 0 := by mytactic

If I'm understanding your suggestion correctly it would require me to violate the "Don't repeat yourself" (DRY) principle by repeating the "some logic" blocks in my code, no?

Eric Wieser (Feb 10 2024 at 10:44):

I think the suggestion is for your array to contain `(tactic| some tactic 1) etc instead

Kyle Miller (Feb 10 2024 at 17:04):

What's the "some logic 1" and "some logic 2"? Could you give a #mwe that represents the functionality of the tactic that you're working on?

Isak Colboubrani (Feb 10 2024 at 18:37):

@Kyle Miller It is code analysing and reporting what progress each "subtactic" achieved. Here is an #mwe:

syntax (name := mytactic) "mytactic" : tactic
@[tactic mytactic] def _mytactic : Lean.Elab.Tactic.Tactic := fun _ =>
  let tactics_1 : Array String := #[
    "some tactic 1",
    "some tactic 2",
    "some tactic 3",
    "aesop"
  ]
  -- let tactics_2 : ...
  -- let tactics_3 : ...
  Lean.Elab.Tactic.withMainContext do
    -- some logic determining which of tactics_1, tactics_2, etc should be run
    -- depending on the current state ...
    tactics_1.forM (λ tactic => do
      -- code analyzing the state after before running the subtactic such as ...
      let unsolved  Lean.Elab.Tactic.getUnsolvedGoals
      Lean.logInfo m!"{unsolved.length} unsolved goal(s) before running subtactic {tactic}"
      match Lean.Parser.runParserCategory ( Lean.MonadEnv.getEnv) `tactic tactic with
        | Except.error e => Lean.logInfo m!"Error '{tactic}': {e}"
        | Except.ok s => Lean.Elab.Tactic.evalTactic s
      -- code analyzing the state after after running the subtactic such as ...
      let unsolved  Lean.Elab.Tactic.getUnsolvedGoals
      Lean.logInfo m!"{unsolved.length} unsolved goal(s) after running subtactic {tactic}"
    )
    -- code analyzing the state after running all subtactics
    let unsolved  Lean.Elab.Tactic.getUnsolvedGoals
    Lean.logInfo m!"{unsolved.length} unsolved goal(s) after running tactics"

example (h :  n, n = 0) :  n, n ^ n = 0 := by mytactic

Mario Carneiro (Feb 10 2024 at 19:29):

@Isak Colboubrani (an important aspect of #mwe's is that they have the correct imports and opens)

Kyle Miller (Feb 10 2024 at 19:38):

Here's an example of how you can organize this:

open Lean Elab Tactic

def evalTacticWithReport (tactic : TSyntax `tactic) : TacticM Unit := do
  let unsolved  Lean.Elab.Tactic.getUnsolvedGoals
  Lean.logInfo m!"{unsolved.length} unsolved goal(s) before running subtactic {tactic}"
  evalTactic tactic
  let unsolved  Lean.Elab.Tactic.getUnsolvedGoals
  Lean.logInfo m!"{unsolved.length} unsolved goal(s) after running subtactic {tactic}"

syntax (name := mytactic) "mytactic" : tactic
@[tactic mytactic] def _mytactic : Lean.Elab.Tactic.Tactic := fun _ =>
  Lean.Elab.Tactic.withMainContext do
    let shouldDoTactics1 := true -- some logic
    if shouldDoTactics1 then
      evalTacticWithReport <|  `(tactic| simp)
      evalTacticWithReport <|  `(tactic| linarith)
      evalTacticWithReport <|  `(tactic| aesop)
    else
      evalTacticWithReport <|  `(tactic| congr)
    -- code analyzing the state after running all subtactics
    let unsolved  Lean.Elab.Tactic.getUnsolvedGoals
    Lean.logInfo m!"{unsolved.length} unsolved goal(s) after running tactics"

Kyle Miller (Feb 10 2024 at 19:39):

Note that withMainContext is only valid for the state right at the beginning. If you need to do some logic after running after tactics, you have to adjust the context.


Last updated: May 02 2025 at 03:31 UTC