Goals and Tactics

Goals and Tactics#

Executing tactics in Pantograph is simple. To start a proof, call the Server.goal_start function and supply an expression.

from pantograph import Server
from pantograph.expr import TacticHave, TacticCalc, TacticExpr
server = Server()
state0 = server.goal_start("forall (p q: Prop), Or p q -> Or q p")

This creates a goal state, which consists of a finite number of goals. In this case since it is the beginning of a state, it has only one goal.

print(state0)
⊢ forall (p q: Prop), Or p q -> Or q p

To execute a tactic on a goal state, use Server.goal_tactic. This function takes a goal id and a tactic. Most Lean tactics are strings.

state1 = server.goal_tactic(state0, goal_id=0, tactic="intro a")
print(state1)
a : Prop
⊢ ∀ (q : Prop), a ∨ q → q ∨ a

Executing a tactic produces a new goal state. If this goal state has no goals, the proof is complete. You can recover the usual form of a goal with str()

str(state1.goals[0])
'a : Prop\n⊢ ∀ (q : Prop), a ∨ q → q ∨ a'

Error Handling and GC#

When a tactic fails, it throws an exception:

try:
    state2 = server.goal_tactic(state1, goal_id=0, tactic="assumption")
    print("Should not reach this")
except Exception as e:
    print(e)
["tactic 'assumption' failed\na : Prop\n⊢ ∀ (q : Prop), a ∨ q → q ∨ a"]

A state with no goals is considered solved

state0 = server.goal_start("forall (p : Prop), p -> p")
state1 = server.goal_tactic(state0, goal_id=0, tactic="intro")
state2 = server.goal_tactic(state1, goal_id=0, tactic="intro h")
state3 = server.goal_tactic(state2, goal_id=0, tactic="exact h")
state3
GoalState(state_id=5, goals=[], _sentinel=[0, 1])

Execute server.gc() once in a while to delete unused goals.

server.gc()

Special Tactics#

Lean has special provisions for some tactics. This includes have, let, calc. To execute one of these tactics, create a TacticHave, TacticLet, TacticCalc instance and feed it into server.goal_tactic.

Technically speaking have and let are not tactics in Lean, so their execution requires special attention.

state0 = server.goal_start("1 + 1 = 2")
state1 = server.goal_tactic(state0, goal_id=0, tactic=TacticHave(branch="2 = 1 + 1", binder_name="h"))
print(state1)
⊢ 2 = 1 + 1
h : 2 = 1 + 1
⊢ 1 + 1 = 2

The TacticExpr “tactic” parses an expression and assigns it to the current goal. This leverages Lean’s type unification system and is as expressive as Lean expressions. Many proofs in Mathlib4 are written in a mixture of expression and tactic forms.

state0 = server.goal_start("forall (p : Prop), p -> p")
state1 = server.goal_tactic(state0, goal_id=0, tactic="intro p")
state2 = server.goal_tactic(state1, goal_id=0, tactic=TacticExpr("fun h => h"))
print(state2)

To execute the conv tactic, use server.goal_conv_begin to enter conv mode on one goal, and use server.goal_conv_end to exit from conv mode. Pantograph will provide interactive feedback upon every tactic executed in conv mode.