Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: Normalizing binder parses
Robert Maxton (Aug 03 2024 at 03:26):
I'm writing an extension to set
to allow it to handle functions the way let
does. Is there a better way of turning a TSyntax `binder
into a TSyntax `funBinder
than this?
/--Turns a ```TSyntax ``binder``` into a```TSyntax ``funBinder```.-/
def binderShim {m} [Monad m] [MonadQuotation m] (b : TSyntax ``binder) :
m (TSyntax ``funBinder) := do
match b with
| `(binder| _) => pure ⟨←`(_)⟩
| `(binder| $id:ident) => pure ⟨id⟩
| `(binder| {$ibs* $[: $ty]?}) =>
let ty ← ty.getDM `(_); pure ⟨←`(funBinder | {$ibs* : $ty})⟩
| `(binder| ⦃$stibs* $[: $ty]?⦄) =>
let ty ← ty.getDM `(_); pure ⟨←`(funBinder | ⦃$stibs* : $ty⦄)⟩
| `(binder| [$[$inst :]? $ty]) => pure ⟨←`(funBinder | [$[$inst :]? $ty])⟩
| `(binder| ($id $ids* $[: $ty]?)) =>
let ids ← ids.mapM (`(⟨·⟩))
let ty ← ty.getDM `(_); pure ⟨←`(funBinder | ($(⟨id⟩) $ids* : $ty))⟩
| _ => unreachable!
Robert Maxton (Aug 03 2024 at 03:30):
Note that almost all the cases are no-ops and can be individually replaced by lines that look like
| `(binder| $ibs:implicitBinder) => pure ⟨←`(funBinder | $ibs:implicitBinder)⟩
I just thought the above was somewhat more aesthetic; but what I can't seem to do is just handle the one distinct case, the case of explicit binders/type ascriptions, and then have all the rest just be
| `(binder| $b) => pure ⟨←`(funBinder | $b)⟩
... at least, not without breaking the thin veneer of type safety with a raw cast, which I'd still rather not do.
Robert Maxton (Aug 03 2024 at 03:37):
Oh, I should say that a TSyntax
binder` is
syntax binder := ppSpace (binderIdent <|> bracketedBinder)
Kyle Miller (Aug 03 2024 at 15:56):
I would avoid redoing any of this processing if possible. The best way to emulate let
is to use let
itself:
import Lean
open Lean Meta Elab Tactic Term
syntax "set'" letDecl (" with " "← "? ident)? : tactic
elab_rules : tactic
| `(tactic| set'%$tk $d:letDecl $[with $[←%$rev]? $h?]?) => withRef tk do
let rev := rev matches some (some _)
let oldHyps ← withMainContext getLocalHyps
let letTac ← `(tactic| let $d:letDecl)
evalTactic letTac
withMainContext do
let newHyps ← getLocalHyps
unless oldHyps.size + 1 = newHyps.size do
throwError "\
Invalid 'set', expecting a single single new local variable. \
Got {newHyps[oldHyps.size:]}."
let new := newHyps[oldHyps.size]!
let some newVal ← new.fvarId!.getValue?
| throwError "\
Invalid 'set', new local variable does not have an associated value. \
This could be caused by pattern matching."
withMainContext do
let rwThm ← mkEq newVal new
let rwPf ← mkExpectedTypeHint (← mkEqRefl new) rwThm
-- let thm ← if rev then mkEq newVal new else mkEq new newVal
-- let thmPf ← mkExpectedTypeHint (← mkEqRefl new) thm
evalTactic (← `(tactic| try rewrite [$(← exprToSyntax rwPf):term] at *))
if let some h := h? then
liftMetaTactic1 fun g => do
let thmPf ← if rev then pure rwPf else mkEqSymm rwPf
let (_, g) ← g.note h.getId thmPf
return g
example : 2 + 3 = 4 := by
set' x := 2 with h
-- ⊢ x + 3 = 4
Kyle Miller (Aug 03 2024 at 15:57):
example : True := by
set' f x := 1 + x with h
/-
f : Nat → Nat := fun x ↦ 1 + x
h : f = fun x ↦ 1 + x
⊢ True
-/
Robert Maxton (Aug 05 2024 at 03:15):
Hm. Wish I had thought of/seen that earlier, before I got the messy parsing already working... :p
But no, you're right, it's better for maintainability to reuse existing machinery when possible. I'll go adjust.
Last updated: May 02 2025 at 03:31 UTC