Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: Manual derecursification of PreDefinition
nrs (Jan 04 2025 at 22:03):
Is it in principle possible to manually turn a PreDefinition
into a kernel-ready recursion-free expression from within a MetaM
declaration? I am looking at Elab/PreDefinition/Structural/BRecOn.lean
and it seems like replaceRecApps
is the key function here. Will be attempting to do this for educational purposes and editing with progress
Kyle Miller (Jan 04 2025 at 22:09):
docs#Lean.Elab.addPreDefinitions will do this and add the definition to the environment. You can get the term by reading the environment. It's TermElabM
, but you can run TermElabM
from within MetaM
.
nrs (Jan 04 2025 at 22:11):
Amazing thanks for the reference! Will attempt a mwe
edit: relevant: #lean4 > How is Nat.below and Nat.brecOn compiled
Kyle Miller (Jan 04 2025 at 22:17):
Maybe you haven't found elabMutualDef
yet? Lean/Elab/MutualDef.lean
That's the main entry point to elaborating definitions/theorems/etc., and I got to addPreDefinitions
by just skimming the code.
nrs (Jan 04 2025 at 22:21):
ah nice I had not seen it yet
nrs (Jan 04 2025 at 22:22):
I wonder if PreDefinition
s are somewhere in the environment or the only way of getting them is by manually building an Expr
edit: oh it seems that the two elabMutualDef
are a bit lower level than PreDefinition
s, they take variations of Array Syntax
edit2: only the elabMutualDef
targeting TermElab
uses addPreDefinitions
so I'll be focusing on that one to start off. in the DefView
s it takes, DefView.value
seems to give the body of a definition as a Syntax
object
edit3: Lean.Elab.Term.MutualClosure.main seems to be the first function in the body of elabMutualDef
that will generate a PreDefinition
edit4: it seems that covers the extent of the existence of PreDefinition
s? produced within elabMutualDef
by MutualClosure.main
, then gets mvars instantiated and universe levels fixed, and then gets passed to addPreDefinitions
edit5: possible ways a PreDefinition
gets consumed in addPreDefinitions
, if no error:
1. nonrec, noncomputable: addNonRec
2. nonrec, computable : addAndCompileNonRec
3. unsafe: addAndCompileUnsafe
4. partial: addAndCompilePartial
if none of these,
5. structuralRecursion
6. wfRecursion
7. (failure: will attempt addAsAxioms
)
edit6: structuralRecursion
or wfRecursion
chosen by matching PreDefinition.termination.terminationBy?
with { structural := true/false, ..}
nrs (Jan 05 2025 at 00:01):
to more closely follow structuralRecursion
and wfRecursion
I'm taking a detour and manually creating a PreDefinition
from a definition identical to Nat.add
. given the above comments I will try to pass this to MutualClosure.main
. The main problem will be to get the Expr
as it is before it is given to elabMutualDef
, I think manually building a let rec
Expr
should do it
edit: it turns this is not that simple and requires reading the stuff above MutualClosure.main
. Am trying to pass the result of applying elabTerm
(with maybe toExpr
) to let rec add (n m : Nat) : Nat := Nat.casesOn m n (fun mm => add n.succ mm); add
to MutualClosure.main
edit2: from MutualDef.lean
:
let rec
f : A := t,
g : B := s;
body
is encoded as
let f : A := ?m₁;
let g : B := ?m₂;
body
where ?m₁
and ?m₂
are synthetic opaque metavariables. The body of f
(or g
) is stored the field val
of a LetRecToLift
.
nrs (Jan 05 2025 at 21:13):
here is my progress so far, from emulating what happens within elabMutualDef
. the following incomplete definition for a PreDefinition
requires a successful call to elabHeaders
; am currently working on understanding on how it works. any tips/suggestions are welcome
import Lean
import Batteries.Tactic.OpenPrivate
open Lean Elab Command Term
open private elabHeaders from Lean.Elab.MutualDef
open private declValToTerminationHint from Lean.Elab.MutualDef
def mkAddPreDef : TermElabM Unit := do
let stx : Syntax <- `(def add (n m : Nat) : Nat := Nat.casesOn m n (fun x => add n.succ x))
let defview := mkDefViewOfDef {} stx[1]
let headerAr <- elabHeaders #[defview] #[] #[]
let headerArEmpty := if headerAr.size = 0 then Bool.true else .false
dbg_trace headerArEmpty -- outputs .true, so call to elabHeaders isn't working properly
let ifWeHaveHeader : DefViewElabHeader -> TermElabM PreDefinition := fun header => do
let termination <- declValToTerminationHint header.value
let predef : PreDefinition := .mk
defview.ref
.def
[]
{}
header.declName
header.type
_ -- PreDefinition.value : Lean.Expr, built within pushMain in MutualDef.lean
termination
pure predef
pure .unit
nrs (Jan 06 2025 at 05:53):
the following seems to produce a minimal PreDefinition
that has not yet undergone derecursification. I don't understand what withAlwaysResolvedPromises
is doing, but omitting it makes elabHeaders
fail, and attempting to manually create the DefViewElabHeader
that elabHeaders
returns will result in failure to recognize the variables n
, m
, in the body, which should be bound by the binders (n : Nat)
and (m : Nat)
. The exact point where going through elabHeaders
vs. creating a DefViewElabHeader
manually will differ seems to be in the body of elabFunValues
, at the line let val ← elabTermEnsuringType valStx type <* synthesizeSyntheticMVarsNoPostponing
. Without elabHeaders
, the symbols n
, m
in the body of the declaration will be replaced by a sorry
. It seems that TermElabM
's Context
and State
are the exact same in the elabHeaders
case vs. the case without, yet the result of the above mentioned line differs. Would be very interested if someone has an explanation for this!
running dbg_trace
on the fields of predef
seems to have the expected outputs, and the term to derecursify seems to be predef.value
. Will attempt to feed this to structuralRecursion
.
import Lean
import Batteries.Tactic.OpenPrivate
open Lean Meta Elab Command Term Language
open private elabHeaders from Lean.Elab.MutualDef
open private withFunLocalDecls from Lean.Elab.MutualDef
open private elabFunValues from Lean.Elab.MutualDef
def mkAddPreDef : TermElabM Unit := do
let stx : Syntax <- `(def add (n : Nat) (m : Nat) : Nat := Nat.casesOn m n (fun x => add n.succ x))
let defview := mkDefViewOfDef {} stx[1]
withAlwaysResolvedPromises 1 fun bodyPromises =>
withAlwaysResolvedPromises 1 fun tacPromises => do
let headers ← elabHeaders #[defview] bodyPromises tacPromises
withFunLocalDecls headers fun funFVars => do
let values ← elabFunValues headers #[] {header := ""}
let predef := (<- Lean.Elab.Term.MutualClosure.main #[] headers funFVars values [])[0]!
pure .unit
edit: the code below is a mwe for the case that attempts to avoid elabHeaders
(and fails). the #eval
will report unknown identifier m/n
. From my tracing, it seems to me that bothState
and Context
in TermElabM
are the same as the case above just before the call to elabFunValues
, so I don't understand why it fails
import Lean
import Batteries.Tactic.OpenPrivate
open Lean Meta Elab Command Term
open private withFunLocalDecls from Lean.Elab.MutualDef
open private elabFunValues from Lean.Elab.MutualDef
def mkAddPreDef : TermElabM Unit := do
let stx : Syntax <- `(def add (n : Nat) (m : Nat) : Nat := Nat.casesOn m n (fun x => add n.succ x))
let defview := mkDefViewOfDef {} stx[1]
let ⟨shortName, declName, levelNames⟩ <- withRef defview.ref do
Term.expandDeclId (<- getCurrNamespace) (<- getLevelNames) defview.declId defview.modifiers
let typeExpr <- elabTerm (<- `(Nat -> Nat -> Nat)) .none
let binders := defview.binders.getArgs
let dveh : DefViewElabHeader <- elabBindersEx binders fun xs => do
Term.synthesizeSyntheticMVarsNoPostponing
let (binderIds, xs) := xs.unzip
let proto_dveh_data : DefViewElabHeaderData := {
declName := declName
shortDeclName := shortName
levelNames := levelNames
binderIds := binderIds
numParams := xs.size
type := typeExpr
}
let proto_dveh : DefViewElabHeader := {
defview, proto_dveh_data with bodySnap? := .none, tacSnap? := .none
}
pure proto_dveh
withFunLocalDecls #[dveh] fun fvars => do
for fvar in fvars do
addLocalVarInfo defview.declId fvar
let funvalues <- elabFunValues #[dveh] #[] {header := ""}
pure .unit
#eval mkAddPreDef
nrs (Jan 11 2025 at 20:30):
continuing with this today, if anyone has inputs it would be highly appreciated! will edit this comment with incomplete progress
edit: currently working on a way to automatically get dependencies between a declaration within a same file (from env.constants.map\2
) in order to make reverse engineering easier
Last updated: May 02 2025 at 03:31 UTC