Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: Working with variables in a command


Chris Henson (Jul 17 2025 at 01:14):

I would like to define a command that given a relation α → α → Prop creates a structure with the relation, using a method that still works if the relation's type contains a variable. Here is an mwe:

structure Bundle (α : Type) where
  rel : α  α  Prop

syntax "create_bundle" ident ident : command
macro_rules
  | `(create_bundle $rel $name) => `(def $name := Bundle.mk $rel)

-- this works fine...
def foo : Nat  Nat  Prop := λ _ _  True
create_bundle foo foo_bundle
#print foo_bundle   --def foo_bundle : Bundle Nat := { rel := foo }


inductive Term (β : Type)
variable {β : Type}
def bar : Term β  Term β  Prop := λ _ _  True

/-
don't know how to synthesize implicit argument 'β'
  @bar ?m.1322
context:
β : Type
-/
create_bundle bar bar_bundle

-- What I want is that $rel uses that mvar in its type signature, e.g
def bar_bundle : Bundle (Term β) := {rel := bar}

I tried elaborating the identifier and inferring its type, but didn't know how work with a variable. Intuitively I want the command to get the first type in a relation α → α → Prop (where α might contain variables) and construct a Bundle α.

Chris Henson (Jul 17 2025 at 16:58):

Here is a bit more of what I've tried, maybe this better communicates what I'm trying to do:

import Lean
open Lean Elab Meta Command Term

structure Bundle (α : Type) where
  rel : α  α  Prop

elab "create_bundle" rel:ident name:ident : command => do
  let rel_type  liftTermElabM do
    -- let's try to get type of $rel, to annotate `Bundle.mk`
    let rel_expr  elabTerm rel none
    let rel_expr  instantiateMVars rel_expr
    let rel_type  inferType rel_expr
    let rel_type  instantiateMVars rel_type
    -- here this seems correct
    logInfo rel_type
    return rel_type
  -- but the same thing here gives an mvar error
  logInfo rel_type
  -- and I still need to use it in the below somehow...
  let defn  `(def $name := Bundle.mk $rel)
  elabCommand defn

def foo : Nat  Nat  Prop := λ _ _  True
-- logInfo:
--  Nat → Nat → Prop
--  Nat → Nat → Prop
create_bundle foo foo_bundle

inductive Term (β : Type)
variable {β : Type}
def bar : Term β  Term β  Prop := λ _ _  True

set_option pp.rawOnError true in
/-
The first logInfo seems correct:
  _root_.Term ?m.2393 → _root_.Term ?m.2393 → Prop

But the second indicates I'm doing something wrong with metavariables?
  [Error pretty printing expression: unknown metavariable '?_uniq.2393'. Falling back to raw printer.]
  (Term ?_uniq.2393) -> (Term ?_uniq.2393) -> Prop
-/
create_bundle bar bar_bundle

Kyle Miller (Jul 17 2025 at 17:02):

What you're seeing in the first log message _root_.Term ?m.2393 → _root_.Term ?m.2393 → Prop is that there are metavariables in the expression. Once you leave liftTermElabM, there's no more metavariable context, so the metavariable definitions are gone, and the term isn't well-formed anymore.

Chris Henson (Jul 17 2025 at 17:09):

Is there some way then for me to make this definition from within the liftTermElabM? Or otherwise indicate that the metavariable there should be the variable β as it is written in the type signature def bar : Term β → Term β → Prop := ...?

Kyle Miller (Jul 17 2025 at 17:15):

Yes, definitely:

Kyle Miller (Jul 17 2025 at 17:15):

import Lean
open Lean Elab Meta Command Term

structure Bundle (α : Type) where
  rel : α  α  Prop

open Lean

elab "create_bundle" rel:ident name:ident : command => do
  liftTermElabM do
    let rel  realizeGlobalConstNoOverloadWithInfo rel
    let ci  getConstInfo rel
    forallTelescope ci.type fun args ty => do
      let throwNotRelation := throwError m!"type{indentExpr ci.type}\nis not a relation"
      unless args.size  2 do
        throwNotRelation
      unless  isDefEq ( inferType args[args.size - 2]!) ( inferType args[args.size - 1]!) do
        throwNotRelation
      unless ( whnf ty).isProp do
        throwError m!"expecting Prop, not{indentExpr ty}"
      let params := ci.levelParams.map .param
      let rel := mkAppN (.const rel params) args[0...args.size-2]
      let bundle  mkAppM ``Bundle.mk #[rel]
      let value  mkLambdaFVars args[0...args.size-2] bundle
      let type  inferType value
      addAndCompile <| .defnDecl {
        name := name.getId
        levelParams := ci.levelParams
        type
        value
        safety := .safe
        hints := .abbrev
      }
      addTermInfo' name (.const name.getId params) (isBinder := true)
      addDeclarationRangesFromSyntax name.getId name

def foo : Nat  Nat  Prop := λ _ _  True

create_bundle foo foo_bundle

#print foo_bundle
/-
def foo_bundle : Bundle Nat :=
{ rel := foo }
-/

inductive Term (β : Type)
variable {β : Type}
def bar : Term β  Term β  Prop := λ _ _  True

create_bundle bar bar_bundle

#check bar_bundle

#print bar_bundle
/-
def bar_bundle : {β : Type} → Bundle (_root_.Term β) :=
fun {β} => { rel := bar }
-/

Chris Henson (Jul 17 2025 at 17:31):

Thanks Kyle, that's perfect!


Last updated: Dec 20 2025 at 21:32 UTC