Zulip Chat Archive

Stream: lean4

Topic: Using Tactics to Generate Theorems


Jeremy Salwen (May 04 2023 at 15:54):

In the following example, I am able to use tactics to generate a new theorem from an old theorem. However, if I try to generalize it to a function I can apply to arbitrary lemmas, lean complains that it can't infer the types. I am curious if this is something that can be worked around, or is there a different approach I need here?

import Lean
import Std.Data.Array.Init.Lemmas
import Mathlib.Init.Function

open Lean Lean.Meta

def f: Nat -> Prop := sorry
def g: Nat -> Nat := sorry
def h: Nat -> Nat -> Nat:= sorry

theorem thm:  (n m:Nat), f (g (h n m)) := sorry

theorem equality:  (n m), g (h n m) = n := sorry

-- This works and generates a theorem `substituted_theorem (n : Nat) : f n`
def substituted_theorem := by
  have x := thm
  simp [equality] at x
  exact x

-- This fails to compile
def convertLemma := λ lem => by
  have x := lem
  simp [equality] at x
  exact x

Sebastian Ullrich (May 04 2023 at 17:42):

Neither fun nor intro allow this; you could write your own intro variant that can deal with mvar types

Jeremy Salwen (May 04 2023 at 17:45):

Could you explain a bit more what you mean? What is the issue with mvar types that intro can't handle, and how would it need to be changed to handle it?

Sebastian Ullrich (May 04 2023 at 17:48):

For one, intro actually works for both function types and lets, so it wouldn't know what to do when the type is an mvar. If your new tactic should only generate function types, that shouldn't be a problem.

Jeremy Salwen (May 04 2023 at 17:51):

I vaguely get the idea that I could try to do something like

def convertLemma := by
  intro lem
  have x := lem
  simp [equality] at x
  exact x

But it wouldn't be able to "pull out" an argument from the goal, which is a metavariable? But what would the type of lem even be at that point?

Kyle Miller (May 04 2023 at 17:51):

Can you say what sorts of things you want to do in general? Will it always be "apply simp with these lemmas to this theorem"?

Jeremy Salwen (May 04 2023 at 17:53):

Kyle Miller said:

Can you say what sorts of things you want to do in general? Will it always be "apply simp with these lemmas to this theorem"?

Yes, basically, but I would probably want to use apply and rw in addition to simp.

Kyle Miller (May 04 2023 at 17:53):

Here's a mathlib metaprogram that does this for a particular domain (it generalizes the target of the theorem and then applies some simp lemmas): https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Tactic/Reassoc.lean

Kyle Miller (May 04 2023 at 17:54):

something that it also gives is a term elaborator, which lets you write theorem foo := reassoc_of% baz

Kyle Miller (May 04 2023 at 17:55):

if that sort of setup is good, then you can skip reading the initialize registerBuiltinAttribute part

Jeremy Salwen (May 04 2023 at 17:57):

Kyle Miller said:

Here's a mathlib metaprogram that does this for a particular domain (it generalizes the target of the theorem and then applies some simp lemmas): https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Tactic/Reassoc.lean

Thank you! This does seem to be doing what I want to.

Kyle Miller (May 04 2023 at 17:57):

Generally, these sorts of programs aren't written as tactic blocks, but instead using the lower-level MetaM monad (and do notation) to construct a proof. It's possible to execute tactic blocks too if you need them.

Jeremy Salwen (May 04 2023 at 17:57):

Yeah, I started off trying to write a MetaM command, but then I slowly realized I could do more and more with tactics, and thought it might be possible to do the entire thing with forward-mode tactics!

Jeremy Salwen (May 04 2023 at 18:34):

I am curious though, is there a way to enter tactic mode to rewrite an Expr inside of the MetaM monad?

Kyle Miller (May 04 2023 at 19:06):

Could you give a concrete example of what you want to do?

Or maybe you're interested in looking at how synthesizeUsing works? (You could take a look at this branch too for some variations.)

Jeremy Salwen (May 04 2023 at 20:04):

Kyle Miller said:

Could you give a concrete example of what you want to do?

Or maybe you're interested in looking at how synthesizeUsing works? (You could take a look at this branch too for some variations.)

For example,

def upLemma (e : Expr) : MetaM Expr := do
 mapForallTelescope (myMagicFunctionToSwitchToTacticsMode (
    do
      have x:= e
      simp [my_lemma] at x
      apply congrArg my_function at x
      exact bar
   )
   ) e

I don't think synthesizeUsing is quite what I'm looking for, since it requires you to know the type you are synthesize ahead of time.

Kyle Miller (May 04 2023 at 20:07):

I believe you can give it a fresh metavariable and make sure the tactic ends up assigning it (like with exact bar, which would do it indirectly via the type of bar)

Jeremy Salwen (May 04 2023 at 20:21):

Kyle Miller said:

I believe you can give it a fresh metavariable and make sure the tactic ends up assigning it (like with exact bar, which would do it indirectly via the type of bar)

Ok, thanks,I have got this so far:

def upLemma (e : Expr) : MetaM Expr := do
 let mvar  Meta.mkFreshExprMVar none
 mapForallTelescope (fun e => synthesizeUsing mvar (do
  apply e
  )
 ) e

I am just not sure how to use the tactic syntax to construct a TacticM, since the above treats apply e as a function call, not a tactic application.

Kyle Miller (May 04 2023 at 20:25):

If you take a look at the branch link, there are some small examples of `(tactic| ... ) syntax and evalTactic.

Kyle Miller (May 04 2023 at 20:30):

Embedding an expression inside the tactic syntax isn't immediate though (and you might consider running the non-interactive tactics that the interactive tactics are frontends to yourself). One function is docs4#Lean.Elab.Term.exprToSyntax, but it's in the TermElabM monad, so you need to do some monad lifting to use it.

Kyle Miller (May 04 2023 at 20:32):

for example docs4#Lean.Meta.apply for the non-interactive version

Jeremy Salwen (May 04 2023 at 20:44):

Hmm, it seems like mapForallTelescope does not work well with this approach:

def upLemma (e : Expr) : MetaM Expr := do
 let mvar  Meta.mkFreshExprMVar none
 mapForallTelescope (fun e => do synthesizeUsing mvar ( `(tactic| exact e))
 ) e

Gives

application type mismatch
  synthesizeUsing mvar __do_lift
argument
  __do_lift
has type
  TSyntax `tactic : Type
but is expected to have type
  Tactic.TacticM Unit : Type

I thought it would be cool to use tactic syntax here, but I think probably it will be simpler to just find the equivalent functions in the MetaM monad.

Kyle Miller (May 04 2023 at 20:47):

The error here is that synthesizeUsing is expecting a Tactic.TacticM Unit, not a Tactic `tactic

Kyle Miller (May 04 2023 at 20:48):

You need a evalTactic in there to convert, like in the linked branch's code

Kyle Miller (May 04 2023 at 20:49):

But there's another problem that the e inside the tactic syntax is not referring to the e outside. You need to use antiquotations and something like the exprToSyntax I mentioned to link them up.

Kyle Miller (May 04 2023 at 20:50):

The `(tactic| ...) quotation should be thought of as being of the same sort of thing as a string (though more interesting than just a sequence of characters). You need to use antiquotations to splice things (ideally Syntax, not Exprs) into it.

Jeremy Salwen (May 05 2023 at 00:38):

Ah, I was misled by the comment here:
https://github.com/leanprover-community/mathlib4/blob/72c82cf2a8e2ed4dadce4e12d5fc1a97a84128ba/Mathlib/Util/SynthesizeUsing.lean#LL42C1-L47C1

let (gs, e)  synthesizeUsing ty ( `(tactic| congr!))

Which I think should be

let (gs, e)  synthesizeUsingTactic ty ( `(tactic| congr!))

I think I understand a bit better now, and I got it working! Many thanks!

import Lean
import Std.Data.Array.Init.Lemmas
import Mathlib.Init.Function
import Mathlib.Util.AddRelatedDecl
import Mathlib.Util.SynthesizeUsing
import Mathlib.Lean.Meta
import Mathlib.Lean.Meta.Simp
import Lean.Elab.Term

open Lean Meta Elab Term Lean.Meta Tactic
open Mathlib.Tactic

def upLemma (e : Expr) : MetaM Expr := do
 let mvar  Meta.mkFreshTypeMVar
 synthesizeUsing mvar (do evalTactic ( `(tactic| exact $( exprToSyntax e))))

elab "upLemma% " t:term : term => do
  upLemma ( elabTerm t none)

def z := upLemma% (@Array.push_data Nat)

Kyle Miller (May 05 2023 at 13:18):

@Jeremy Salwen Sorry for the documentation being misleading (and for being a beta tester!). I've corrected it and added a more sophisticated example based on your code: https://github.com/leanprover-community/mathlib4/blob/9cb74d7cb7610ada87159591070e2a21da966c79/Mathlib/Util/SynthesizeUsing.lean#L69-L78

It turns out there's a big gotcha with this approach that took me a while to get to the bottom of. I was testing out

( `(tactic| have h := $( Term.exprToSyntax e); simp at h; exact h))

for a simpTerm% term elaborator. It looks fine, but in the case I was trying (simpTerm% h where h : a + 0 = b) it didn't appear to be simplifying!

It turns out that if simp applies a rfl lemma to a local hypothesis, it doesn't really do anything to the proof term and it just changes the type. Since a + 0 = a is true by definition for Nat, then while simp at h was giving h : a = b, the exact h would have the original unsimplified type.

There's a fix though: do exact id h instead. This captures the type of h as it appears in the local context.

Kyle Miller (May 05 2023 at 13:19):

I thought I'd mention this in case you also run into mysterious errors.

Mario Carneiro (May 05 2023 at 13:27):

there is a function like mkExpectedTypeHint for handling this

Kyle Miller (May 05 2023 at 13:32):

Yeah, I think the underlying issue is that simp uses docs4#Lean.MVarId.replaceLocalDeclDefEq, which isn't using mkExpectedTypeHint to record the change...

Kyle Miller (May 05 2023 at 13:33):

so this exact id h thing is a workaround. I guess it could also be used after synthesizeUsing to add a hint using mvar as the new type (part of this is just an exercise to do as much as possible in a tactic script rather than using non-interactive tactics)

Kyle Miller (May 05 2023 at 13:50):

Here's with mkExpectedTypeHint:

def simpTerm (e : Expr) : MetaM Expr := do
  let mvar  Meta.mkFreshTypeMVar
  let e'  synthesizeUsing' mvar
    (do evalTactic ( `(tactic| have h := $( Term.exprToSyntax e); simp at h; exact h)))
  Meta.mkExpectedTypeHint e' mvar

elab "simpTerm% " t:term : term => do simpTerm ( Term.elabTerm t none)

variable (h : a + 0 = b)
#check simpTerm% h
-- ... : a = b

Jeremy Salwen (May 05 2023 at 15:04):

@Kyle Miller Thanks so much for helping with this!


Last updated: Dec 20 2023 at 11:08 UTC