Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: Elaborating a DSL that uses Lean's binders / PHOAS
Nate Sandy (Jul 07 2025 at 08:20):
I'm trying to write a DSL that has binders, but uses Lean's binders to represent them. I followed the PHOAS example and am now trying to add an elaborator, but struggling with that.
The first issue is that "result contains metavariables". It seems that ?rep is a metavariable in the first #reduce. I think what I want to do here is add rep as an implicit argument, but I don't know how.
The second issue is that I don't know how to actually "use" the bound variable when elaborating identifiers. (See comment.)
Also, if there's a better technique to define a DSL which uses Lean's binders, I'm all ears!
import Lean
open Lean Elab Command Term Meta
inductive LambType where
| nat
| fn : LambType → LambType → LambType
inductive LambTerm' (rep : LambType → Type) : LambType → Type
| var : rep ty → LambTerm' rep ty
| const : Nat → LambTerm' rep .nat
| abst : (rep dom → LambTerm' rep ran) → LambTerm' rep (.fn dom ran)
def LambTerm (ty : LambType) := {rep : LambType → Type} → LambTerm' rep ty
def identity : LambTerm (.fn .nat .nat) :=
.abst (fun x => .var x)
declare_syntax_cat lamb_expr
syntax "lamb" ident "." lamb_expr : lamb_expr
syntax ident : lamb_expr
syntax num : lamb_expr
partial def elabLamb : Syntax → TermElabM Expr
| `(lamb_expr| $x:ident) => do
-- just a placeholder. how do i "use" the bound variable
-- that was created by the lamb $x:ident . $b:lamb_expr elaboration?
mkAppM ``LambTerm'.const #[mkNatLit 1]
| `(lamb_expr| lamb $x:ident . $b:lamb_expr) => do
let type ← mkFreshTypeMVar
withLocalDeclD x.getId type fun fvar => do
let b ← elabLamb b
let abst ← mkLambdaFVars #[fvar] b
mkAppM ``LambTerm'.abst #[abst]
| `(lamb_expr| $n:num) =>
mkAppM ``LambTerm'.const #[mkNatLit n.getNat]
| _ => throwUnsupportedSyntax
elab "test_elabLamb " l:lamb_expr : term => elabLamb l
set_option pp.explicit true
-- AppBuilder for 'mkAppM', result contains metavariables
-- @LambTerm'.const ?rep (@OfNat.ofNat Nat (nat_lit 3) (instOfNatNat (nat_lit 3)))
#reduce test_elabLamb 3
-- AppBuilder for 'mkAppM', result contains metavariables
-- @LambTerm'.const ?rep (@OfNat.ofNat Nat (nat_lit 1) (instOfNatNat (nat_lit 1)))
#reduce test_elabLamb lamb x . x
Kiran (Aug 07 2025 at 16:09):
did you solve this? was there a particular reason you were implementing it in TermElab instead of at the macro level? i.e
partial def expandLamb : TSyntax `lamb_expr -> MacroM (TSyntax `term)
| `(lamb_expr| $n:num) => do
`(term| LambTerm'.const $n)
| `(lamb_expr| $v:ident) => do
`(term| LambTerm'.var $v)
| `(lamb_expr| lamb $x . $expr:lamb_expr) => do
let expr <- expandLamb expr
`(term| LambTerm'.abst (fun $x => $expr))
| _ => Macro.throwUnsupported
elab "lamb! " l:lamb_expr : term => do
let stx <- liftMacroM $ expandLamb l
elabTerm stx .none
Nate Sandy (Sep 26 2025 at 11:10):
I did manage to create the missing metavariable, but ultimately ended up not using PHOAS for my DSL.
Last updated: Dec 20 2025 at 21:32 UTC