Zulip Chat Archive
Stream: lean4
Topic: easy metaprogramming questions
Kevin Buzzard (Jul 17 2022 at 13:13):
As well as not being very good at programming, I'm also not very good at knowing how to learn a programming language; I am always amazed by people who say "I read the source code" -- I just don't know where to start. Forgive me then for the basic question.
I've been reading the metaprogramming tutorial by Arthur and others and I've just about muddled through to the end.
The code just above this URL https://github.com/arthurpaulino/lean4-metaprogramming-book/blob/master/md/main/tactics.md#tweaking-the-context (i.e. the end of the section before that one) is
elab "custom_assump_2" : tactic =>
Lean.Elab.Tactic.withMainContext do
let goal ← Lean.Elab.Tactic.getMainGoal
let goalType ← Lean.Elab.Tactic.getMainTarget
let ctx ← Lean.MonadLCtx.getLCtx
let option_matching_expr ← ctx.findDeclM? fun decl: Lean.LocalDecl => do
let declExpr := decl.toExpr
let declType ← Lean.Meta.inferType declExpr
if ← Lean.Meta.isExprDefEq declType goalType
then return Option.some declExpr
else return Option.none
match option_matching_expr with
| some e => Lean.Elab.Tactic.closeMainGoal e
| none =>
Lean.Meta.throwTacticEx `custom_assump_2 goal
(m!"unable to find matching hypothesis of type ({goalType})")
(this is a walkthrough of building an assumption
tactic) and right now I understand the code well enough to just about see what it does (although even that I'm not too clear on -- for example I don't really understand why the program is looping through all the local declarations and not just testing the first one and letting option_matching_expr
be none
if its type is not equal to the goal type). But I don't understand what's going on well enough to modify it. For example I am being told how to write "if hypothesis type = goal type then close goal" but I do not know how to write "if hypothesis type is a <= _
and goal type is a <= _
then do refine le_trans <hypothesis> _
". How do I learn about how to ask specific more refined questions about expressions?
Yakov Pechersky (Jul 17 2022 at 13:31):
Kevin, regarding looping on all of them, I think findDeclM? does exit early on the first match. If it can't find any, you get the none value.
Arthur Paulino (Jul 17 2022 at 13:39):
So, this involves a trade-off. Let me try to express my thoughts.
First, it's important to say that the current implementation of custom_assump_2
is quite more complicated than it could be (see this version by Sebastian). While it's more complicated, it also exposes more of the API, motivating the reader to do some digging.
Now, the harsh part is that it contains some not-so-simple functional programming monadic tricks, which makes me feel like I'm betraying my own words from the intro chapter. And this makes me feel like simply sticking to Sebastian's implementation.
The trick I'm referring to is exactly what Yakov pointed out. findDeclM
does the iteration on the declarations available in the context, moving on while none
is being returned by fun decl: Lean.LocalDecl => ...
and stopping on the first occurrence of some _
.
A more refined question on the iterating expression declType
could be made by replacing the if ← Lean.Meta.isExprDefEq declType goalType ...
block by the check you want to perform
Arthur Paulino (Jul 17 2022 at 13:46):
I'd love to have input from more people on the didactic of this part. It takes the reader to a walk and slowly introduces new mechanisms
Damiano Testa (Jul 17 2022 at 13:49):
I find that an inefficient tactic for a tutorial is not necessarily a bad idea. I really enjoyed Rob Lewis' video where he implements the assumption tactic, by going through the "first attempt everyone would try" and then golfing it. It would be very instructive to have something similar, but I do not know how to do that in a book...
Arthur Paulino (Jul 17 2022 at 14:04):
Ah, I just realized that I didn't really address Kevin's question.
As far as I know, we don't have pretty syntax sugar for matching expressions in Lean 4 as we do in Lean 3 yet. Gabriel wrote quote4, which does make things easier for that purpose but I don't know if there's a plan to make something like that available from Lean 4 core out of the box.
The easiest answer I know is that we can use matches like in this example from the MetaM
chapter:
def matchAndReducing (e : Expr) : MetaM (Option (Expr × Expr)) := do
let e ← whnf e
match e with
| (.app (.app (.const ``And _) P) Q) => return some (P, Q)
| _ => return none
Henrik Böving (Jul 17 2022 at 14:05):
To also answer the question about operating more precisely on Expr
s here is my take that would work right now...given some patience:
The easiest way for this would of course be to pattern match on expressions... Gabriel has been working on a library that could enable this in the future and there was at least an idea to have it in a usable state by now but I don't think he got there yet (?)
Either way, my approach for figuring out how things that I don't have an idea how they work are is: Try to think where someone might have implemented something similar already and dig in their stuff a bit. For example in this case we want to know how to ask more precise questions about Expr
. My first thought would be to either:
- Check the API docs for
Expr
for a function that sounds like it might help you (either the docs4#Lean.Expr documentation page and search or just your editors autocompletion). In a fully developed programming language this should work out in the majority of cases but as we know Lean 4 isn't fully there yet so this requires a little more precise idea of what you want to do for now. - I know that the
ring
tactic is already implemented in mathlib4 and that it somehow turnsExpr
into Horner form and operates on them so they must definitely figure out how the expressions look. And if we go to the source code here: https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Tactic/Ring.lean we see that they have a typeHornerExpr
https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Tactic/Ring.lean#L84 quite far up, now I would suspect that they have some function that turns anExpr
into aHornerExpr
(probably a monadic one because it can fail and what not but never the less such a function does probably exist) so we can grep for a line that contains these words:grep "def .*Expr.*HornerExpr" Ring.lean
(of course one could also manually dig the file) and we'll find (among a few others): https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Tactic/Ring.lean#L382 and at the core of the translation here we have docs4#Lean.Expr.getAppFnArgs which:Return the function (name) and arguments of an application.
So what we have to do if we want to know whether we have a hyp of the fitting form is:
- Run the
getAppFnArgs
on the goal type, check whether the function name isLE.le
and remember the first argument (thea
in your example) - Iterate over our hypothesis, run
getAppFnArgs
on them as well, check if the function name isLE.le
and if their left hand sideisExprDefEq
to thea
we remembered from step 1 - Apply the hypothesis with
refine
(where how to apply refine is again found by grepping and searching docs)
And thus I end up with:
import Mathlib
import Lean
open Lean Elab Tactic Meta
elab "kevin_refine_assump" : tactic =>
-- familiar
withMainContext do
let goal ← getMainGoal
let goalType ← getMainTarget
-- step 1
let a ← match Expr.getAppFnArgs goalType with
| (``LE.le, #[_, _, a, _]) => pure a -- 4 arguments because of implicits
| _ => throwTacticEx `kevin_refine_assump goal "Goal doesn't match!"
-- familiar
let ctx ← Lean.MonadLCtx.getLCtx
let hyp? ← ctx.findDeclM? fun decl: Lean.LocalDecl => do
let declType ← Lean.Meta.inferType decl.toExpr
-- step 2
match Expr.getAppFnArgs declType with
| (``LE.le, #[_, _, a_hyp, _]) =>
if ←isExprDefEq a a_hyp then
return some decl
else
return none
| _ => return none
match hyp? with
| some hyp =>
-- step 3
let name := mkIdent hyp.userName
let stx ← `(le_trans $name _)
refineCore stx `kevin_refine_assump false -- I found refineCore by looking for "refine" on the docs page
| none => throwTacticEx `kevin_refine_assump goal "No matching hypothesis found!"
So the general approach to learn something in Lean (and generally in software world) that is not documented and you dont want to ask somebody about how it works is: Try to think of a thing that most likely already does the part you are stuck on and attempt to figure out how they did that.
Siddhartha Gadgil (Jul 17 2022 at 14:58):
I don't exactly understand what is being sought, but if one wants to match expressions, one powerful way in lean 4 is to create metavariables as holes and use isDefEq
. In case this is of the nature intended, below is some code to match an expression for a natural number.
/-- natural number from expression built from `Nat.zero` and `Nat.succ` -/
partial def exprNat : Expr → TermElabM Nat := fun expr =>
do
let mvar ← mkFreshExprMVar (some (mkConst ``Nat))
let sExp ← mkAppM ``Nat.succ #[mvar]
let expr ← reduce expr
Term.synthesizeSyntheticMVarsNoPostponing
if ← isDefEq sExp expr then
Term.synthesizeSyntheticMVarsNoPostponing
let prev ← exprNat (← whnf mvar)
return Nat.succ prev
else
if ← isDefEq (mkConst `Nat.zero) expr then
return Nat.zero
else
throwError m!"{expr} not a Nat expression"
-- test
elab "nat!" t:term : term => do
let e ← elabTerm t none
let n ← exprNat e
return ToExpr.toExpr n
#eval nat! (123 + 4) -- 127
Siddhartha Gadgil (Jul 17 2022 at 15:00):
I should clarify the above is defensive to handle various cases. The line Term.synthesizeSyntheticMVarsNoPostponing
can be ignored.
Kevin Buzzard (Jul 17 2022 at 19:14):
What is being sought is basic understanding! Thanks so much for all these pointers!
Last updated: Dec 20 2023 at 11:08 UTC