Zulip Chat Archive

Stream: lean4

Topic: Learning about metaprogramming


Adam Topaz (Jul 23 2022 at 23:37):

Hi all, I wanted to learn a little bit of metaprogramming in Lean4, so I am trying to write a version of mathlib's apply_fun tactic.
So far, I have something fairly rudimentary with the following code (note: this is still very far from the full capability of mathlib's apply_fun tactic):

import Lean

open Lean Elab.Tactic

syntax "applyFun " term " at " ident : tactic

elab_rules : tactic
| `(tactic|applyFun $f:term at $i:ident) =>
  withMainContext do
    let goal  getMainGoal
    match ( getLCtx).findFromUserName? i.getId with
    | some d => do
        evalTactic $  `(tactic|have $i := congrArg $f $i)
        try replaceMainGoal [ Meta.clear ( getMainGoal) d.fvarId]
        catch | _ => return
    | none => Meta.throwTacticEx `applyFun goal f!"Hypothesis named {i} not found."

example (a b : Nat) (f : Nat  α) (h : a = b) : f a = f b := by
  applyFun f at h
  exact h

This seems to work (at least in the example) when we actually apply a function.
However, one thing I cannot figure out is how to make this work if f has some type that has an appropriate CoeFun instance (the mathlib Lean3 version is able to do this, and much more!). Can someone point me in the right direction here?
Also, I am very new to metaprogramming and programming in Lean4 in general, any advice about improving the code above would be much appreciated!

David Renshaw (Jul 23 2022 at 23:51):

Style-wise, the concrete syntax should remain apply_fun. See https://github.com/leanprover-community/mathlib4/blob/dda51df299ce844cc686ecbd6f08c17a4a247078/Mathlib/Mathport/Syntax.lean#L426

Arthur Paulino (Jul 23 2022 at 23:55):

I think you want to do

try
  evalTactic $  `(tactic|have $i := congrArg $f $i)
  replaceMainGoal [ Meta.clear ( getMainGoal) d.fvarId]
catch _ => Meta.throwTacticEx `applyFun goal "tactic failed"

So even the have foo := congrArg ... is guarded from errors

Arthur Paulino (Jul 23 2022 at 23:57):

I've never done instance inference in metaprogramming before, but I believe you could try mkAppM with the inferInstance function unless someone else tells us a more appropriate method

Arthur Paulino (Dec 07 2022 at 15:45):

How do I do this without writing syntax in my elaborator?

let natInt  elabTerm ( `(Nat × Int)) none

Alexander Bentkamp (Dec 07 2022 at 15:52):

let natInt  mkAppM ``Prod #[mkConst ``Nat, mkConst ``Int]

Arthur Paulino (Dec 07 2022 at 15:54):

Thanks! I had done

mkAppM ``Prod.mk ...

before, but I suspected I had done something wrong

Jannis Limperg (Dec 07 2022 at 16:33):

(FYI const ``Nat [] is now the preferred form and will save you from the mkConst footgun of forgetting to specify the universe levels. Also, here you can use mkAppN instead of mkAppM.)

Arthur Paulino (Dec 07 2022 at 19:28):

Jannis Limperg said:

(FYI const ``Nat [] is now the preferred form and will save you from the mkConst footgun of forgetting to specify the universe levels. Also, here you can use mkAppN instead of mkAppM.)

Where is const?

Arthur Paulino (Dec 07 2022 at 19:30):

Ah, Expr.const probably

Scott Morrison (Dec 07 2022 at 22:34):

Regarding the mkConst footgun, mathlib4 now defines (in Mathlib.Lean.Expr.Basic)

/-- Same as `mkConst`, but with fresh level metavariables. -/
def mkConst' (constName : Name) : MetaM Expr := do
  return mkConst constName ( ( getConstInfo constName).levelParams.mapM fun _ => mkFreshLevelMVar)

Better names and/or homes welcome if someone wants to move it upstream.

Gabriel Ebner (Dec 08 2022 at 05:42):

I believe the function should be called mkConstWithFreshMVarLevels and should be put in Lean/Meta/Basic.lean, roughly around here.


Last updated: Dec 20 2023 at 11:08 UTC