Zulip Chat Archive

Stream: general

Topic: proof mode: split out tactics into separate commands


Mario Carneiro (Aug 12 2023 at 17:52):

Mario Carneiro said:

But if lean had a proof mode like HOL light's e(...) steps broken out as separate lean commands then you would certainly get back the speed benefits you are seeing in HOL Light

Mario Carneiro said:

now I think I want to try to demo something like that in lean

Here's what I was talking about @D. J. Bernstein : This is a prototype implementation of a "proof mode" in which you can save the context of a proof in a by block by writing start_proof, and then you can start writing tactics in separate commands thereafter using ## before each tactic (or group of tactics). Because lean automatically snapshots between commands, you get the benefits of a line-by-line proof like in Coq or HOL light in terms of general responsiveness. This may be considered as an alternative to the save tactic, which tries to do something similar but without linearizing the AST structure.

Mario Carneiro (Aug 12 2023 at 17:53):

Mathlib.Tactic.ProofMode:

import Lean
open Lean

structure ProofState where
  tacCtx : Elab.Tactic.Context
  tacState : Elab.Tactic.State
  termCtx : Elab.Term.Context
  termState : Elab.Term.State
  metaCtx : Meta.Context
  metaState : Meta.State

def ProofState.get : Elab.Tactic.TacticM ProofState :=
  fun tacCtx tacState termCtx termState metaCtx metaState => do
    let tacState  tacState.get
    let termState  termState.get
    let metaState  metaState.get
    return { tacCtx, tacState, termCtx, termState, metaCtx, metaState }

def ProofState.with : ProofState  Elab.Tactic.TacticM α  CoreM α :=
  fun { tacCtx, tacState, termCtx, termState, metaCtx, metaState } x => do
    let tacState  IO.mkRef tacState
    let termState  IO.mkRef termState
    let metaState  IO.mkRef metaState
    x tacCtx tacState termCtx termState metaCtx metaState

initialize proofStateExt : EnvExtension (List ProofState)  registerEnvExtension (pure [])

elab "start_proof" : tactic => do
  let state  ProofState.get
  modifyEnv (proofStateExt.modifyState · (state :: ·))
  Elab.Tactic.evalTactic ( `(tactic| repeat sorry))

elab tk:"##" tac:(tacticSeq)? : command => do
  let state :: rest := proofStateExt.getState ( getEnv)
    | throwError "proof mode not started, use `start_proof`"
  Elab.Command.liftTermElabM do
    state.with do
      Elab.Tactic.withTacticInfoContext tk do
        if let some tac := tac then
          Elab.Tactic.evalTactic tac
      if ( Elab.Tactic.getUnsolvedGoals).isEmpty then
        logInfoAt tk "Goals accomplished 🎉"
        modifyEnv (proofStateExt.setState · rest)
      else
        modifyEnv (proofStateExt.setState · (( ProofState.get) :: rest))

Mario Carneiro (Aug 12 2023 at 17:54):

test file:

import Mathlib.Tactic.ProofMode

theorem T :  x, x < x + 1 := by
  constructor
  start_proof -- this pushes the current proof state (note, it doesn't work in `example`)

## -- cursor here to see proof state
## refine Nat.lt_succ_self ?_ -- you can write a tactic or tactic block here
## exact 1 -- when the goal is finished, it pops the proof state and reports "Goals accomplished 🎉"

Mario Carneiro (Aug 12 2023 at 19:02):

It would be nice to get some experience reports with this from people who like to write long tactic proofs and struggle to keep things responsive

D. J. Bernstein (Aug 12 2023 at 19:17):

Will try with my next Lean project, thanks!


Last updated: Dec 20 2023 at 11:08 UTC