How to match up macro-generated and user-defined identifiers?

The question is likely very basic. I'm just rusty :grin:

I'm trying to implement poor man's module that can be parameterized by types, using structs. An example is I can do something like:

(define-signature (expr E)
  ref : (Symbol β†’ E)
  app : (E E β†’ E)
  lam : (Symbol E β†’ E))

(define-expr-implementation interp : ((Env β†’ V))
  (define ((ref x) ρ) (ρ x))
  (define ((app e₁ eβ‚‚) ρ) ((e₁ ρ) (eβ‚‚ ρ)))
  (define ((lam x e) ρ) (λ (v) (e (ext ρ x v)))))

(define-expr-implementation stage : ((Syntaxof Any))
  ...)

And have generated code like below:

(struct (E) expr ([ref : (Symbol β†’ E)]
                  [app : (E E β†’ E)]
                  [lam : (Symbol E β†’ E)]))

(define interp : (expr (Env β†’ V))
   (let ()
     (define-type E (Env β†’ V))

     (: ref : (Symbol β†’ E))
     (: app : (E E β†’ E))
     (: lam : (Symbol E β†’ E))

     (define ((ref x) ρ) (ρ x))
     (define ((app e₁ eβ‚‚) ρ) ((assert (e₁ ρ) procedure?) (eβ‚‚ ρ)))
     (define ((lam x e) ρ) (λ ((v : V)) (e (ext ρ x v))))

     (expr ref app lam)))

(define stage : (expr (Syntaxof Any))
   (let ()
     (define-type E (Syntaxof Any))
     
     (: ref : (Symbol β†’ E))
     (: app : (E E β†’ E))
     (: lam : (Symbol E β†’ E))
     
     ...

     (expr ref app lam)))

My current macro looks like below, and the problem I'm having is the bindings in the macro-generated type annotations don't match the bindings in the user-provided implementation. I tried stripping their context with (format-id #f ...) and (datum->syntax #f (syntax->datum ...)), but that didn't work. What's the easiest way I can make the bindings match up?

(define-syntax-parser define-signature
  [(_ (sig:id t:id ...) (~seq (~seq x:id (~literal :) tβ‚“:expr) ...))
   (with-syntax ([define-impl (format-id #'sig "define-~a-implementation" (syntax->datum #'sig))])
     #`(begin
         (struct (t ...) sig ([x : tβ‚“] ...) #:transparent)
         (define-syntax-parser define-impl
           [(_ impl:id : (t* (... ...)) d (... ...))
            (with-syntax ([(x* (... ...)) (map (Ξ» (z) (format-id #f "~a" z)) '(x ...))]
                          [(t** (... ...)) '(t ...)]
                          [(tβ‚“* (... ...)) '(tβ‚“ ...)]
                          [(d (... ...))
                           (datum->syntax #f (syntax->datum #'(d (... ...))))])
              (define stx
                #'(define impl : (sig t* (... ...))
                    (let ()
                      (define-type t** t*) (... ...)
                      (: x* : tβ‚“*) (... ...)
                      d (... ...)
                      (sig x* (... ...)))))
              (printf "Generated:~n")
              (pretty-print (syntax->datum stx))
              stx)])))])

;; Error from generated code:
; Type Checker: Declaration for `ref' provided, but `ref' has no definition
;   in: ref

An unrelated question: I'm making define-signature generate a macro for each signature just because I don't know how to remember the struct information across different macros. If I want to do something like below, where define-implementation knows about the field names and types of expr, how do I do that? I tried (syntax-property expr ...) but that didn't work, and (define-for-syntax info ...) wasn't going to retain the state across different expansions.

(define-implementation interp : (expr (Env β†’ V))
  ...)

Many thanks!!

I’m not sure what’s the β€œproper” way, but simply replacing (not stripping!) the contexts works:

(define-syntax-parser define-signature
  [(_ (sig:id t:id ...) (~seq x:id (~literal :) tβ‚“:expr) ...)
   #:with define-impl (format-id #'sig
                                 "define-~a-implementation" #'sig)
   #'(begin
       (struct (t ...) sig ([x : tβ‚“] ...) #:transparent)
       (define-syntax-parser define-impl
         [(_ impl:id : (t* (... ...)) d (... ...))
          #:with (x* (... ...)) (replace-context #'impl #'(x ...))
          #:with (t** (... ...)) #'(t ...)
          #:with (tβ‚“* (... ...)) #'(tβ‚“ ...)
          #'(define impl : (sig t* (... ...))
              (let ()
                (define-type t** t*) (... ...)
                (: x* : tβ‚“*) (... ...)
                d (... ...)
                (sig x* (... ...))))]))])

Edit: And to the second question, define-syntax bindings can be accessed through syntax-local-value. This is how macros work in the first place (they are just procedures bound this way, nothing special).

1 Like

Thank you very much!!