-
Notifications
You must be signed in to change notification settings - Fork 435
Metaprogramming for dummies
This is an overview to the most common functions that are useful for writing tactics in Lean 4. We assume some knowledge of functional programming and a basic understanding of how metaprogramming works in Lean 4. The standard references are Lean 4 Metaprogramming book and Functional Programming in Lean, respectively.
The abstract Monad m
is built out of two operations, (>>=)
(also known as bind
) and return
(also known as pure
).
These have the signature (>>=) : m α → (α → m β) → m β
and return : α → m α
.
Thinking about monads as types carrying around state, the bind operation takes a stateful variable a : m α
function f : α → m β
,
'unpacks' a
, inserts it into f
and combines the state.
In practise we don't use (>>=)
by hand, instead we use do
notation, which is very handy syntactic sugar for chains of (>>=)
(and in Lean 4 it also allows for automatic lifting of monads.
Todo:
- Examples for
do
notation with corresponding(>>=)
version. - Example for lifting where the direction version is complicated.
We also mention some monadic higher order functions that are very useful to write efficient code:
-
Monad.join:
combine the state of
m (m α)
tom α
For iterating monadic functions over lists and arrays (to be found in the List
and Array
namespace, respectively):
-
mapM
: takes a monadic functionf : α → m β
and a list of inputsList α
and appliesf
to each input and returns a monadic list of outputsm (List β)
. -
filterM
: takes a monadic functionf : α → m Bool
and a list of inputsList α
and filters the input list byf
returningm (List α)
. -
foldlM
andfoldrM
: take a monadic functionf : s → α → m s
, an initial valuea : s
and aList α
and iteratesf
using the list in each step as parameters and returns the resultm s
. The difference betweenfoldlM
andfoldrM
is that direction in which the elements ofList α
are taken (left vs. right).
There are a lot of monads that are used in metaprogramming, but for tactics we can get away with using only two monads:
-
MetaM is the monad you want to use if you modify metavariables.
-
TacticM is the topmost monad for tactic-writing. You can get and set goals, etc
We also mention TermElabM
which is the monad for term elaboration and it sits in between MetaM
and TacticM
. Using the Quote4
(also called Qq
) library (see information on that below) one can do all elaboration of user-supplied expressions in TacticM
and 'internal' elaboration in MetaM
using Qq
.
Lean has two different types for syntax: Lean.Syntax and Lean.TSyntax (typed syntax), where the later is a dependent type to allow compile-time checks of syntax. The kind of typed syntax is defined in Lean.SyntaxNodeKinds, which is just a List Lean.Name
.
- elabTerm
- elabTermForApply
- elabTermEnsuringType
withLocalDeclD
open Lean.Expr
Check whether something is a metavariable: Create metavariable: Assign metavariable: Check whether it is assigned
Note about delayed assignments (and reference to meta-book)
isBVar
isLambda lambdaTelescope
isForall forallTelescope
- isApp
- getAppFn
- getAppNumArgs
- getAppArgs
-
Expr.getAppFnArgs
: This is useful if you have an expression and you want to pattern-match
open Lean.Elab.Tactic
getGoals
getMainGoal
setGoals
open Lean.Elab.Tactic
evalTactic
withMainContext
liftMetaTactic
Lean.MVarId.apply
Example:
- If no universe levels:
mvarId.apply (mkConst ``foo [])
- If known universe levels:
mvarId.apply (mkConst ``foo [u,v])
- Otherwise:
mvarId.apply (← mkConstWithFreshMVarLevels ``foo)
However, see also: Qq
.
- The easy stuff
- Qq (Qq is love, Qq is life).
If you want to build expressions, it's a good idea to use Qq
instead of manually building them from the constructors. The Q(α)
macro is a synonym of Expr
, with the (unchecked?) assertion that any e : Q(α)
typechecks to have type α
. And you can build elements of Q
using q
, and it will automatically, statically, fill in implicits. Instances are looked up in the context of the q
invocation.
Unfortunately, errors generated by Qq
are hard to understand.
-
dbg_trace s!"foo {var}"
is the most lowlevel way of debugging anything -- printsfoo
and whatevervar
is in the local context -
logInfo s!"foo {var}"
is slightly more sophisticated and only works in monads that support logging (in particular inMetaM
andTacticM
) -
trace[identifier] s!"foo {var}"
is the serious way of logging output. To declare a trace class invokeinitialize registerTraceClass identifier
and then activate the logging withset_option trace.identifier true
. Note that the logging does not work in the file where the trace class is defined.