Zulip Chat Archive

Stream: lean4

Topic: metaprogramming: how to create function with structural rec?


Michael Jam (Jun 16 2022 at 17:46):

I'm trying to create a function g with a command. g should have the same body as f.
There is a recursive call in g's body which is causing an issue.
Here is a minimized example :

import Lean
open Lean Elab Command Term Meta

def f : Nat  Nat :=
  fun k => match k with
  | 0 => 42
  | n + 1 => f n

elab "the_answer" : command => do
  let name := `g
  let type <- liftCoreM <| MetaM.run' <| do
    let stx <- `(Nat  Nat)
    let r <- TermElabM.run' <| elabTerm stx .none
    return r
  let val <- liftCoreM <| MetaM.run' <| do
    let stx <- `(
      fun k => match k with
      | 0 => 42
      | n + 1 => g n
    )
    let r <- TermElabM.run' <| withDeclName name <| elabTerm stx (.some type)
    let r <- instantiateMVars r
    return r
  let decl := Declaration.defnDecl <| mkDefinitionValEx name [] type val .opaque .safe
  match (<- getEnv).addAndCompile {} decl with
  | .error e => throwError (e.toMessageData {})
  | .ok env => setEnv env

the_answer
#print g

It fails because it is unaware of g in this function body context. I tried using a let rec, but g remains an unassigned metavariable.
How can I fix my code?

Thanks for suggestions or solutions

Arthur Paulino (Jun 16 2022 at 21:20):

I do think that let rec is a good solution:

    let stx <- `(
      let rec aux := fun k => match k with
      | 0 => 42
      | n + 1 => aux n
      aux
    )

The other issue (kernel) declaration has metavariables 'g' is related to something else I can't point my finger at yet

Arthur Paulino (Jun 16 2022 at 21:23):

Unrelated: you probably want to use elabTermEnsuringType

Alex Keizer (Jun 16 2022 at 21:56):

Alternatively, just generate syntax for the entire definition and feed that to elabCommand

open Lean Lean.Elab.Command in
elab "the_answer" : command => do
  let name := mkIdent `g
  elabCommand <|<- `(
    def $name : Nat  Nat
      | 0 => 42
      | n + 1 => $name n
  )

the_answer

#check g
#eval g 0
#eval g 10

Note the mkIdent needed to ensure hygiene does not mess up the name of your function.

Michael Jam (Jun 17 2022 at 07:47):

@Alex Keizer Well that doesn't exactly solve my issue, because I was actually working in AttrM where I cannot use elabCommand. But thanks for that idea, I'm probably just gonna change my plans and make a command instead of an attribute...

Jannis Limperg (Jun 17 2022 at 08:01):

You can lower TermElabM or CommandElabM into CoreM, then lift that into AttrM. Something like this:

def runTermElabMAsCoreM (x : TermElabM α) : CoreM α :=
  x.run' |>.run' {} {}

This runs the elaboration with an empty metavariable context and some dummy elaboration state.

Jannis Limperg (Jun 17 2022 at 08:01):

(The lifting from CoreM to AttrM is automatic via coercion.)

Sebastian Ullrich (Jun 17 2022 at 08:02):

AttrM in fact is just an abbrev for CoreM nowadays

Jannis Limperg (Jun 17 2022 at 08:07):

It's the identity coercion. :innocent:

Sebastian Ullrich (Jun 17 2022 at 08:09):

Just to summarize the original topic: the structural recursion compiler is part of the command (declaration) elaborator, yes, so elaborating a single term is not sufficient (except for nested let recs, which are kind of separate declarations). If anyone had a need for structural recursion without going through one of the existing declaration elaborators, they would need to look at invoking docs4#Lean.Elab.Structural.structuralRecursion manually.


Last updated: Dec 20 2023 at 11:08 UTC