7.7. カスタムタクティク(Custom Tactics)🔗

タクティクは構文カテゴリ tactic に含まれるプロダクションです。タクティクの構文が与えられると、タクティクのインタプリタはタクティクモナド TacticM でアクションを実行します。これは Lean の項エラボレータのラッパーで、タクティクの実行にあたって追加で状態を追跡するものです。カスタムタクティクは tactic カテゴリの拡張と、以下のいずれかから構成されます:

  • 新しい構文を既存の構文に変換する macro

  • TacticM アクションを実行してタクティクを実装するエラボレータ

7.7.1. タクティクマクロ(Tactic Macros)🔗

新しいタクティクを定義する最も簡単な方法は、 macro として既存のタクティクに展開することです。マクロ展開はタクティクの実行と同時に行われます。タクティクのインタプリタは、まずタクティクマクロを解釈する直前に展開します。タクティクマクロはタクティクスクリプトを実行する前に完全には展開されないため、再帰を使用することができます;マクロ構文の再帰的な出現が実行可能なタクティクの下にある限り、マクロの展開は無限に連鎖しません。

再帰的なタクティクマクロ

以下の repeat に似たタクティクの再帰的な実装は、マクロ展開によって定義されます。引数 $t が失敗した時、 rep の再帰的な発生は絶対に呼び出されず、したがってマクロ展開も決して行われません。

syntax "rep" tactic : tactic macro_rules | `(tactic|rep $t) => `(tactic| first | $t; rep $t | skip) example : 0 4 := 04 rep (Nat.le 0 0) All goals completed! 🐙

他の Lean のマクロと同様に、タクティクマクロは hygienic です。グローバル名への参照はマクロが定義された時に解決され、タクティクマクロによって導入された名前はその呼び出しサイトから名前をキャプチャすることはできません。

タクティクマクロを定義するとき、パターンマッチと構成に用いられる構文が構文カテゴリ tactic のものであることを指定することが重要です。そうでない場合、構文は項の構文として解釈され、タクティクのための正しくない AST とマッチしたり構成されたりします。

7.7.1.1. 拡張可能なタクティクマクロ(Extensible Tactic Macros)🔗

マクロの展開は失敗することがあるため、複数のマクロが同じ構文にマッチすることができ、バックトラックが可能になります。タクティクマクロはこれをさらに推し進めます:タクティクマクロの展開が成功しても解釈時に展開に失敗すると、タクティクのインタプリタは次の展開を試みます。これは Lean の多くの組み込みタクティクを拡張可能にするためにしようされます。 Lean.Parser.Command.macro_rules : commandmacro_rules 宣言を追加することで新しい動作をタクティクに追加できます。

trivial の拡張

trivial はユーザを煩わせる価値のないサブゴールを素早くディスパッチするために、他の多くのタクティクから使用されますが、新しいマクロ展開によって拡張できるように設計されています。Lean のデフォルトの trivialIsEmpty [] ゴールを解決できません:

def IsEmpty (xs : List α) : Prop := ¬ xs [] example (α : Type u) : IsEmpty (α := α) [] := α:Type uIsEmpty [] α:Type uIsEmpty []

このエラーメッセージは trivial が最後に assumption を試したことによるものです。ここで別の拡張を追加することで trivial がこれらのゴールを対応するようにできます:

def emptyIsEmpty : IsEmpty (α := α) [] := α:Type u_1IsEmpty [] All goals completed! 🐙 macro_rules | `(tactic|trivial) => `(tactic|exact emptyIsEmpty) example (α : Type u) : IsEmpty (α := α) [] := α:Type uIsEmpty [] All goals completed! 🐙
展開のバックトラック

マクロ展開は、展開された構文のいずれかの部分で失敗した場合にバックトラックを引き起こす可能性があります。複数の展開を別々の Lean.Parser.Command.macro_rules : commandmacro_rules 宣言で定義することで、 first の中置バージョンを定義することができます:

syntax tactic "<|||>" tactic : tactic macro_rules | `(tactic|$t1 <|||> $t2) => pure t1 macro_rules | `(tactic|$t1 <|||> $t2) => pure t2 example : 2 = 2 := 2 = 2 All goals completed! 🐙 <|||> apply And.intro example : 2 = 2 := 2 = 2 apply And.intro <|||> All goals completed! 🐙

複数の Lean.Parser.Command.macro_rules : commandmacro_rules 宣言は、それぞれがパターンマッチ関数を定義し、常に最初にマッチする選択肢を取ることができるようにするために必要です。バックトラックは Lean.Parser.Command.macro_rules : commandmacro_rules 宣言の粒度であり、個々のケースではありません。

7.7.2. タクティクモナド(The Tactic Monad)🔗

Planned Content
  • Relationship to Lean.Elab.Term.TermElabM, Lean.Meta.MetaM

  • Overview of available effects

  • Checkpointing

Tracked at issue #67

🔗def
Lean.Elab.Tactic.Tactic : Type
🔗def
Lean.Elab.Tactic.TacticM (α : Type) : Type
🔗def
Lean.Elab.Tactic.run (mvarId : Lean.MVarId)
    (x : TacticM Unit)
    : Lean.Elab.TermElabM (List Lean.MVarId)
🔗def
Lean.Elab.Tactic.runTermElab {α : Type}
  (k : Lean.Elab.TermElabM α)
  (mayPostpone : Bool := false) : TacticM α

Runs a term elaborator inside a tactic.

This function ensures that term elaboration fails when backtracking, i.e., in first| tac term | other.

7.7.2.1. 制御(Control)

🔗def
Lean.Elab.Tactic.tryTactic {α : Type}
    (tactic : TacticM α) : TacticM Bool
🔗def
Lean.Elab.Tactic.tryTactic? {α : Type}
    (tactic : TacticM α) : TacticM (Option α)

7.7.2.2. 式(Expressions)

🔗def
Lean.Elab.Tactic.ensureHasNoMVars
    (e : Lean.Expr) : TacticM Unit
🔗def
Lean.Elab.Tactic.getFVarId (id : Lean.Syntax)
    : TacticM Lean.FVarId
🔗def
Lean.Elab.Tactic.getFVarIds
    (ids : Array Lean.Syntax)
    : TacticM (Array Lean.FVarId)
🔗def
Lean.Elab.Tactic.sortMVarIdsByIndex
    {m : Type → Type} [Lean.MonadMCtx m]
    [Monad m] (mvarIds : List Lean.MVarId)
    : m (List Lean.MVarId)
🔗def
Lean.Elab.Tactic.sortMVarIdArrayByIndex
    {m : Type → Type} [Lean.MonadMCtx m]
    [Monad m] (mvarIds : Array Lean.MVarId)
    : m (Array Lean.MVarId)

7.7.2.3. ソース位置(Source Locations)

🔗def
Lean.Elab.Tactic.withLocation (loc : Location)
    (atLocal : Lean.FVarIdTacticM Unit)
    (atTarget : TacticM Unit)
    (failed : Lean.MVarIdTacticM Unit)
    : TacticM Unit

Runs the given atLocal and atTarget methods on each of the locations selected by the given loc.

  • If loc is a list of locations, runs at each specified hypothesis (and finally the goal if is included), and fails if any of the tactic applications fail.

  • If loc is *, runs at the target first and then the hypotheses in reverse order. If atTarget closes the main goal, withLocation does not run atLocal. If all tactic applications fail, withLocation with call failed with the main goal mvar.

7.7.2.4. ゴール(Goals)

🔗def
Lean.Elab.Tactic.getGoals
    : TacticM (List Lean.MVarId)

Returns the list of goals. Goals may or may not already be assigned.

🔗def
Lean.Elab.Tactic.setGoals
    (mvarIds : List Lean.MVarId) : TacticM Unit
🔗def
Lean.Elab.Tactic.getMainGoal
    : TacticM Lean.MVarId

Return the first goal.

🔗def
Lean.Elab.Tactic.getMainTag : TacticM Lean.Name

Return the main goal tag.

🔗def
Lean.Elab.Tactic.closeMainGoal
  (tacName : Lean.Name) (val : Lean.Expr)
  (checkUnassigned : Bool := true) :
  TacticM Unit

Closes main goal using the given expression. If checkUnassigned == true, then val must not contain unassigned metavariables. Returns true if val was successfully used to close the goal.

🔗def
Lean.Elab.Tactic.focus {α : Type}
    (x : TacticM α) : TacticM α
🔗def
Lean.Elab.Tactic.tagUntaggedGoals
    (parentTag newSuffix : Lean.Name)
    (newGoals : List Lean.MVarId) : TacticM Unit

Use parentTag to tag untagged goals at newGoals. If there are multiple new untagged goals, they are named using <parentTag>.<newSuffix>_<idx> where idx > 0. If there is only one new untagged goal, then we just use parentTag

🔗def
Lean.Elab.Tactic.getUnsolvedGoals
    : TacticM (List Lean.MVarId)
🔗def
Lean.Elab.Tactic.pruneSolvedGoals : TacticM Unit
🔗def
Lean.Elab.Tactic.appendGoals
    (mvarIds : List Lean.MVarId) : TacticM Unit

Add the given goals at the end of the current list of goals.

🔗def
Lean.Elab.Tactic.closeMainGoalUsing
  (tacName : Lean.Name)
  (x :
    Lean.ExprLean.NameTacticM Lean.Expr)
  (checkNewUnassigned : Bool := true) :
  TacticM Unit

Try to close main goal using x target tag, where target is the type of the main goal and tag is its user name.

If checkNewUnassigned is true, then throws an error if the resulting value has metavariables that were created during the execution of x. If it is false, then it is the responsibility of x to add such metavariables to the goal list.

During the execution of x:

  • The local context is that of the main goal.

  • The goal list has the main goal removed.

  • It is allowable to modify the goal list, for example with Lean.Elab.Tactic.pushGoals.

On failure, the main goal remains at the front of the goal list.

7.7.2.5. 項エラボレーション(Term Elaboration)

🔗def
Lean.Elab.Tactic.elabTerm (stx : Lean.Syntax)
  (expectedType? : Option Lean.Expr)
  (mayPostpone : Bool := false) :
  TacticM Lean.Expr

Elaborate stx in the current MVarContext. If given, the expectedType will be used to help elaboration but not enforced (use elabTermEnsuringType to enforce an expected type).

🔗def
Lean.Elab.Tactic.elabTermEnsuringType
  (stx : Lean.Syntax)
  (expectedType? : Option Lean.Expr)
  (mayPostpone : Bool := false) :
  TacticM Lean.Expr

Elaborate stx in the current MVarContext. If given, the expectedType will be used to help elaboration and then a TypeMismatchError will be thrown if the elaborated type doesn't match.

🔗def
Lean.Elab.Tactic.elabTermWithHoles
  (stx : Lean.Syntax)
  (expectedType? : Option Lean.Expr)
  (tagSuffix : Lean.Name)
  (allowNaturalHoles : Bool := false)
  (parentTag? : Option Lean.Name := none) :
  TacticM (Lean.Expr × List Lean.MVarId)

Elaborates stx and collects the MVarIds of any holes that were created during elaboration.

With allowNaturalHoles := false (the default), any new natural holes (_) which cannot be synthesized during elaboration cause elabTermWithHoles to fail. (Natural goals appearing in stx which were created prior to elaboration are permitted.)

Unnamed MVarIds are renamed to share the tag parentTag? (or the main goal's tag if parentTag? is none). If multiple unnamed goals are encountered, tagSuffix is appended to this tag along with a numerical index.

Note:

  • Previously-created MVarIds which appear in stx are not returned.

  • All parts of elabTermWithHoles operate at the current MCtxDepth, and therefore may assign metavariables.

  • When allowNaturalHoles := true, stx is elaborated under withAssignableSyntheticOpaque, meaning that .syntheticOpaque metavariables might be assigned during elaboration. This is a consequence of the implementation.

7.7.2.6. 低レベル操作(Low-Level Operations)

これらの操作は主に TacticM や特定のタクティクの実装の一部として使用されます。新しいタクティクを実装する際に役に立つことは稀です。

7.7.2.6.1. モナドクラスの実装(Monad Class Implementations)

これらの操作は標準的な Lean のモナド型クラスを通して公開されます。

🔗def
Lean.Elab.Tactic.tryCatch {α : Type}
    (x : TacticM α)
    (h : Lean.ExceptionTacticM α) : TacticM α

Non-backtracking try/catch.

🔗def
Lean.Elab.Tactic.liftMetaMAtMain {α : Type}
    (x : Lean.MVarIdLean.MetaM α) : TacticM α
🔗def
Lean.Elab.Tactic.getMainModule
    : TacticM Lean.Name
🔗def
Lean.Elab.Tactic.orElse {α : Type}
    (x : TacticM α) (y : UnitTacticM α)
    : TacticM α

7.7.2.6.2. マクロ展開(Macro Expansion)

🔗def
Lean.Elab.Tactic.getCurrMacroScope
    : TacticM Lean.MacroScope
🔗def
Lean.Elab.Tactic.adaptExpander
    (exp : Lean.SyntaxTacticM Lean.Syntax)
    : Tactic

Adapt a syntax transformation to a regular tactic evaluator.

7.7.2.6.3. ケース分析(Case Analysis)

🔗def
Lean.Elab.Tactic.elabCasesTargets
    (targets : Array Lean.Syntax)
    : TacticM
      (Array Lean.Expr ×
        Array (Lean.Ident × Lean.FVarId))

7.7.2.6.4. 単純化器(Simplifier)

🔗def
Lean.Elab.Tactic.elabSimpArgs
    (stx : Lean.Syntax)
    (ctx : Lean.Meta.Simp.Context)
    (simprocs : Lean.Meta.Simp.SimprocsArray)
    (eraseLocal : Bool) (kind : SimpKind)
    : TacticM ElabSimpArgsResult

Elaborate extra simp theorems provided to simp. stx is of the form "[" simpTheorem,* "]" If eraseLocal == true, then we consider local declarations when resolving names for erased theorems (- id), this option only makes sense for simp_all or * is used. When recover := true, try to recover from errors as much as possible so that users keep seeing the current goal.

🔗def
Lean.Elab.Tactic.elabSimpConfig
    (optConfig : Lean.Syntax) (kind : SimpKind)
    : TacticM Lean.Meta.Simp.Config
🔗def
Lean.Elab.Tactic.elabSimpConfigCtxCore
    : Lean.SyntaxTacticM Lean.Meta.Simp.ConfigCtx
🔗def
Lean.Elab.Tactic.dsimpLocation'
    (ctx : Lean.Meta.Simp.Context)
    (simprocs : Lean.Meta.Simp.SimprocsArray)
    (loc : Location)
    : TacticM Lean.Meta.Simp.Stats

Implementation of dsimp?.

🔗def
Lean.Elab.Tactic.elabDSimpConfigCore
    : Lean.SyntaxTacticM Lean.Meta.DSimp.Config

7.7.2.6.5. 属性(Attributes)

🔗
Lean.Elab.Tactic.tacticElabAttribute
    : Lean.KeyedDeclsAttribute Tactic
🔗unsafe def
Lean.Elab.Tactic.mkTacticAttribute
    : IO (Lean.KeyedDeclsAttribute Tactic)