Zulip Chat Archive

Stream: lean4

Topic: Using Tactics to Generate `=` Theorems


Markus Schmaus (Nov 29 2023 at 19:12):

I've been looking into how to use tactics in order to generate theorems, rather than just proving them. This is primarily meant for simple theorems similar to docs#Function.comp_def or docs#List.map_eq_map.

Here is what I have come up with:

import Mathlib

macro "transform" f:term " => " cs:Lean.Parser.Tactic.Conv.convSeq : tactic => do
  `(tactic| (
      have eq : $f = $f := rfl
      conv at eq =>
        rhs
        ($cs)
      exact eq))

def f (n : Nat) : Nat :=
  n * 2

def g (n : Nat) : Nat :=
  2 * f n

@[simp]
def g_def := by
  transform g =>
    unfold g
    unfold f

#check g_def

example :  g = fun n => 2 * (n * 2) := by
  simp?


def h (n : Nat) : Nat :=
  3 * f n

def map_g_def (xs : List Nat) := by
  transform h <$> [1, 2, 3] =>
    simp only [Functor.map, List.map_eq_map]
    unfold h
    unfold f
    ring_nf

#check map_g_def

Writing transform term => puts me into conversation mode which allows me to use tactics to simplify the term. The most useful tactics for this is probably unfold, but other tactics like simp or ring work as well.

This is my first foray into meta programming, so any feedback would be highly welcomed. It only works with def and not with theorem, does this make any difference? Also, the goal only shows up once I write my first tactic, can this be fixed?

Kyle Miller (Nov 29 2023 at 20:08):

@Markus Schmaus This is a nice idea. I made a prototype for a full definition command for these, so for example

transform map_g_def : h <$> [1, 2, 3] =>
  simp only [Functor.map, List.map_eq_map, List.map]
  unfold h
  unfold f
  ring_nf

#check map_g_def
-- map_g_def : h <$> [1, 2, 3] = [6, 12, 18]

I didn't go as far as making this support binders like theorem would, and it might be buggy, but at least it lets you use variable to add extra binders.

Kyle Miller (Nov 29 2023 at 20:09):

Here's my implementation. I couldn't find a way to use the theorem command directly so I had to copy its implementation (and doubtless get some of it wrong).

import Mathlib

open Lean Meta Elab Tactic

syntax declModifiers "transform " ident " : " term " => " Parser.Tactic.Conv.convSeq : command

elab_rules : command
  | `($mods:declModifiers transform%$tk $id : $t =>%$arr $code) => do
    let mods  elabModifiers mods
    Elab.Command.runTermElabM fun args => do
      let e  Elab.Term.elabTermAndSynthesize t none
      withRef tk do
        let aux : TacticM (Expr × Expr) := withRef (mkNullNode #[tk, arr]) do
          let (e', proof)  Elab.Tactic.Conv.convert e (withTacticInfoContext ( getRef) (evalTactic code))
          return ( mkEq e e', proof)
        let ((thm, pf), _)  aux { elaborator := .anonymous } |>.run { goals := [] }
        let fvars := (collectFVars (collectFVars {} thm) pf).fvarIds
        let args := args.filter fun arg => fvars.contains arg.fvarId!
        let thm'  mkForallFVars args thm
        let pf'  mkLambdaFVars args pf
        let thm'  Elab.Term.levelMVarToParam ( instantiateMVars thm')
        if thm'.hasMVar then
          throwError "Theorem has unassigned metavariables{indentD thm'}"
        let pf'  instantiateMVars pf'
        if pf'.hasLevelMVar then
          throwError "Proof has universe level metavariables{indentD pf'}"
        if pf'.hasMVar then
          throwError "Proof has unassigned metavariables{indentD pf'}"
        let decl  withRef id do
          Term.expandDeclId ( getCurrNamespace) ( Term.getLevelNames) id mods
        addAndCompile <| Declaration.thmDecl
          { name := decl.declName
            levelParams := decl.levelNames
            type := thm'
            value := pf' }
        -- For hovering on id:
        withSaveInfoContext do
          Term.addTermInfo' id ( mkConstWithLevelParams decl.declName) (isBinder := true)
        -- Process attributes:
        Term.applyAttributesAt decl.declName mods.attrs .afterTypeChecking
        -- Not sure if this is necessary for theorems:
        Term.applyAttributesAt decl.declName mods.attrs .afterCompilation

def f (n : Nat) : Nat :=
  n * 2

def g (n : Nat) : Nat :=
  2 * f n

variable (n : Nat)

@[simp]
transform g_def : g n =>
  unfold g
  unfold f

example : g 2 = 2 * (2 * 2) := by
  simp

#check g_def
-- g_def (n : Nat) : g n = 2 * (n * 2)

def h (n : Nat) : Nat :=
  3 * f n

transform map_g_def : h <$> [1, 2, 3] =>
  simp only [Functor.map, List.map_eq_map, List.map]
  unfold h
  unfold f
  ring_nf

#check map_g_def
-- map_g_def : h <$> [1, 2, 3] = [6, 12, 18]

Last updated: Dec 20 2023 at 11:08 UTC