Zulip Chat Archive

Stream: new members

Topic: elaboration, inferType, mkExists


Mark Aagaard (Nov 18 2024 at 01:06):

I am learning my around elaboration and type inference. As an exercise, I'm trying to write a macro and elaborator to convert foo 5 into ∃ x : Nat, x = 5. I'm having difficulty getting the monads to line up so that the elaborator typechecks.

import Lean
import Lean.Meta

open Lean
open Lean.Elab
open Lean.Elab.Term
open Command
open Meta

syntax (name := foox) "foo" term  : term

-- this returns the argument to `foo`
@[term_elab foox]
def elabFoo1 : TermElab := fun stx typ? => do
  -- throwError s!"entry point: 0={stx[0]} 1={stx[1]} 1.0={stx[1][0]} {typ?}"
  let val := stx[1]
  let mt  := elabTerm val none
  mt

#eval foo 5 -- returns 5

-- this doesn't typecheck, because the result is Expr when it should be TermElabM Expr
def elabFoo2 : Syntax  Option Expr  TermElabM Expr := fun stx typ? => do
  -- throwError s!"entry point: 0={stx[0]} 1={stx[1]} 1.0={stx[1][0]} {typ?}"
  let val := stx[1] -- get the argument to foo
  let mt  := elabTerm val none -- elaborate the argument to get a TermElabM Expr
  let bod   mt -- extract the Expr from the monad
  let ty    inferType bod -- infer the type
  let res := Lean.mkForall `x Lean.BinderInfo.default ty bod  -- build the result Expr
  res -- type = Expr, expected type = TermElabM Expr

The second problem is that I haven't found a mkExists that works like mkForall or mkForallEx.

-mark

Matt Diamond (Nov 18 2024 at 01:37):

At the risk of giving you bad advice: have you considered doing pure res instead of just res?

I have no understanding of your code or metaprogramming in Lean in general, but when it comes to monads, whenever you have a term of type a and you need a term of type m a, often using pure (or return) will do the trick.

Mark Aagaard (Nov 18 2024 at 02:10):

Matt,
Thanks. Adding in pure led to some progress. The code below type checks, but throws an error.

@[term_elab foox]
def elabFoo1 : TermElab := fun stx typ? => do
  tryPostponeIfNoneOrMVar typ?
  let val := stx[1]
  let bod  elabTerm val none -- setting expected type to none, bad idea?
  let ty   inferType bod
  let res := Lean.mkForall `x Lean.BinderInfo.default ty bod
  pure res

#eval foo 5
-- failed to synthesize instance
--  OfNat (Sort ?u.747) 5

-mark

Kyle Miller (Nov 18 2024 at 02:45):

The issue here is that you're constructing the expression forall x : ty, 5, and there's no instance that makes 5 : Sort _ make sense.

Kyle Miller (Nov 18 2024 at 02:46):

Maybe this helps?

syntax (name := foox) "foo" term  : term

@[term_elab foox]
def elabFoo1 : TermElab := fun stx typ? => do
  match stx with
  | `(foo $val) =>
    let bod  elabTerm val none
    let ty  inferType bod
    withLocalDeclD `x (mkConst ``Nat) fun x => do
      let eq  mkEq x bod
      let res  mkForallFVars #[x] eq
      return res
  | _ => throwUnsupportedSyntax

#check foo 5
-- ∀ (x : Nat), x = 5 : Prop

Kyle Miller (Nov 18 2024 at 02:47):

(Tip: use #check here instead of #eval, since you aren't wanting to evaluate the expression, just see what it elaborates to.)

Matt Diamond (Nov 18 2024 at 03:01):

@Kyle Miller would the code be similar to construct ∃ (x : Nat), x = 5? I think that was @Mark Aagaard's next question and tbh I'm kind of curious too

Kyle Miller (Nov 18 2024 at 03:04):

Yes, the first difference would be to use mkLambdaFVars instead of mkForallFVars, and then you pass that to the last argument of Exists

Kyle Miller (Nov 18 2024 at 03:05):

syntax (name := foox) "foo" term  : term

@[term_elab foox]
def elabFoo1 : TermElab := fun stx typ? => do
  match stx with
  | `(foo $val) =>
    let bod  elabTerm val none
    let ty  inferType bod
    withLocalDeclD `x ty fun x => do
      let eq  mkEq x bod
      let res  mkLambdaFVars #[x] eq
      mkAppM ``Exists #[res]
  | _ => throwUnsupportedSyntax

set_option pp.funBinderTypes true
#check foo 5
-- ∃ (x : Nat), x = 5 : Prop

Mark Aagaard (Nov 18 2024 at 03:15):

Kyle-
Thanks! Your solution is very informative.
-mark

Michael Bucko (Nov 18 2024 at 15:53):

So the core Elab is Generalize + Change + Rfl + Symm + Rewrite? Then it's the simplification layer - Simp / NormCast / SimpTrace ? Then some automation (like SolveByElim), some logical "features" like FalseOrByContra, induction and some transformation mechanisms (I see 4? Conv, Unfold, Split, Delta)

And then building an elab is essentially accessing the syntax tree, applying transformations, and updating the proof state? (one can also declare the custom syntax)


Last updated: May 02 2025 at 03:31 UTC