Macros expanding to generics

101 views
Skip to first unread message

Alex Knauth

unread,
Mar 22, 2017, 7:50:23 PM3/22/17
to Racket Developers
I'm having trouble writing macros that expand to uses of generics properly. Depending on what I do in different instances it either "silently" fails to implement the method or results in an ambiguous binding error.

(My end goal with this is to wrap macros around generics to do type-checking on those parts of the program, using the method of Type Systems as Macros.)

The very simplest version of my problem is a macro that uses gen:custom-write in it's own scope:

#lang agile

(define-simple-macro
  (my-struct name [field ...] #:custom-write [method-def ...])
  (struct name [field ...] #:methods gen:custom-write [method-def ...]))

(my-struct foo [a]
  #:custom-write
  [(define (write-proc this out mode)
     (fprintf out "~v" (foo-a this)))])

The problem here is that the struct form assumes that the method definitions will be in the same lexical context as the use of gen:custom-write. This can be solved by using `datum->syntax` or `syntax-local-introduce`. That makes it unhygienic, but that's fine so far. 

The next version of my problem is a macro that takes its own notion of a generic interface and needs to expand into racket's generics.

#lang agile

(begin-for-syntax
  (struct interface-info [internal-id]))

(define-syntax my-custom-write
  (interface-info #'gen:custom-write))

;; my structs specify generics using the #:my-methods keyword,
;; which expects an identifier bound with define-syntax to an
;; interface-info struct
(define-simple-macro
  (my-struct name [field ...] #:my-methods gen [method-def ...])
  #:with internal-id (interface-info-internal-id (syntax-local-value #'gen))
  (struct name [field ...] #:methods internal-id [method-def ...]))

(my-struct foo [a]
  #:my-methods my-custom-write
  [(define (write-proc this out mode)
     (fprintf out "~v" (foo-a this)))])

This has the same problem, but with an added indirection. The actual `gen:custom-write` identifier comes from someplace else, the innards of `my-custom-write`. Because of this I can't just `datum->syntax` it or `syntax-local-introduce` it, it could be imported from another file!

;; interface-info.rkt
#lang agile
(begin-for-syntax
  (provide (struct-out interface-info))
  (struct interface-info [internal-id]))

;; my-custom-write.rkt
#lang agile
(provide my-custom-write)
(require "interface-info.rkt")
(define-syntax my-custom-write
  (interface-info #'gen:custom-write)) ; gen:custom-write is bound in this scope

With this, using `syntax-local-introduce` on the internal id has the same problem of failing to implement the method:

(define-simple-macro
  (my-struct name [field ...] #:my-methods gen [method-def ...])
  #:with internal-id (syntax-local-introduce
                      (interface-info-internal-id (syntax-local-value #'gen)))
  (struct name [field ...] #:methods internal-id [method-def ...]))

And trying to use `make-syntax-delta-introducer` results in an ambiguous binding error:

(define-simple-macro
  (my-struct name [field ...] #:my-methods gen [method-def ...])
  #:with internal-id ((make-syntax-delta-introducer #'gen #false)
                      (interface-info-internal-id (syntax-local-value #'gen)))
  (struct name [field ...] #:methods internal-id [method-def ...]))

And using two deltas to remove the internal-id scopes and add the gen scopes seems to work at first:

(define-simple-macro
  (my-struct name [field ...] #:my-methods gen [method-def ...])
  #:with internal-id (interface-info-internal-id (syntax-local-value #'gen))
  #:with internal-id* ((make-syntax-delta-introducer #'gen #false)
                       ((make-syntax-delta-introducer #'internal-id #false)
                        #'internal-id
                        'remove)
                       'add)
  (struct name [field ...] #:methods internal-id [method-def ...]))

Until you realize that completely destroyed the original binding of the internal-id so that it fails on a simple example like:

(define gen:custom-write "I wouldn't attempt to interfere with bindings within other files")

(my-struct foo [a]
  #:my-methods my-custom-write
  [(define (write-proc this out mode)
     (fprintf out "~v" (foo-a this)))])
;. my-custom-write.rkt:5:20: struct: the first argument to the #:methods specification is not a name for a generic interface in: gen:custom-write

Without destroying the binding of the internal-id to gen:custom-write from the other file, how do I do this properly? Is the ambiguous binding error a sign that I'm getting closer? Is there any other way to introduce generic interfaces that would be more friendly to macros?

(By the way, my last example of defining a new gen:custom-write is not contrived. It is necessary because the new definition is the typed version.)

Alex Knauth

Matthew Flatt

unread,
Mar 24, 2017, 3:08:22 PM3/24/17
to Alex Knauth, Racket Developers
Your pull request

https://github.com/racket/racket/pull/1644

helps clarify the issue to me, and it looks like the right kind of
solution.
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Developers" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-dev+...@googlegroups.com.
> To post to this group, send email to racke...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/racket-dev/2F051005-C8F0-4225-9CE3-08E44FEE89
> C6%40knauth.org.
> For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages