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