[racket] macro help redux

From: Todd O'Bryan (toddobryan at gmail.com)
Date: Wed Jun 9 08:23:07 EDT 2010

OK. I've also attached a zipped folder that has the two files.

Here's aug-struct.rkt:

#lang racket
(require (for-syntax racket/struct-info))

(provide define-aug-struct)

;; stolen from define-struct
(define-for-syntax (build-name id . parts)
    (apply string-append
           (map (lambda (p)
                    [(syntax? p) (symbol->string (syntax-e p))]
                    [(symbol? p) (symbol->string p)]
                    [(string? p) p]))

;; this unifies the super's info with the sub's info, allowing
;; you to override defaults and contract info
(define-for-syntax (unify super-info sub-info)
  (append (map (λ (sup)
                 (let ([sub (assoc (car sup) sub-info)])
                   (or sub sup)))
          (filter (λ (sub) (not (assoc (car sub) super-info))) sub-info)))

;; the general idea is that, assuming sup is either another aug-struct
;; or #f,
;; (define-aug-struct (sub sup)
;;   ([field1 contract1]
;;    [field2 contract2 default2]
;;    ...))
;; should be expanded to
;; (define-syntax sub-info
;;   '([field1 contract1] [field2 contract2 default2] ...))
;;   so I can grab this info later to unify it with any sub-structs
;; (define-struct (sub sup)
;;   (field2))        assumes field1 was defined in sup
;; (define (create-sub #:field1 field1
;;                     #:field2 [field2 default2])
;;    (make-sub field1 field2))
;; (provide/contract
;;     [create-sub (-> #:field1 contract1
;;                     #:field2 contract2
;;                     sub?)])
(define-syntax (define-aug-struct stx)
  (syntax-case stx ()
    [(_ (id super-id) field-info-syntax)
     ; TODO: error-checking
         ([id+maybe-super (if (syntax->datum #'super-id)
                              #'(id super-id)
          [super-info (if (syntax->datum #'super-id)
                          (syntax-local-value (build-name #'super-id
#'super-id "-info"))
          [id-name (syntax->datum #'id)]
          [field-info (syntax->datum #'field-info-syntax)]
          [unified-info (unify super-info field-info)]
          [subfield-names (map car (filter (λ (info)
                                             (not (assoc (car info)
          [all-field-names (map car unified-info)]
          [kw-names (map (λ (fld)
                           (string->keyword (symbol->string fld)))
          [create-id (build-name #'id "create-" id-name)]
          [no-default (gensym)]
          [defaults (map (λ (info)
                           (if (= 2 (length info))
                               (caddr info)))
          [create-formals (cons create-id
                                (foldr append '()
                                       (map (λ (kw fld def)
                                              (if (equal? no-default def)
                                                  (list kw fld)
                                                  (list kw (list fld def))))
                                            kw-names all-field-names
          [contracts (map cadr unified-info)]
          [create-contract (cons '->
                                 (foldr append `(,(build-name #'id id-name "?"))
                                        (map (λ (kw contr)
                                               (list kw contr))
                                             kw-names contracts)))]
          [make-id (build-name #'id "make-" id-name)]
          [id-info (build-name #'id id-name "-info")])
       (with-syntax ([unified-info-syntax unified-info])
             (define-syntax #,id-info (syntax->datum #'unified-info-syntax))
             (define-struct #,id+maybe-super
             (define #,create-formals
               (#,make-id #, at all-field-names))
             (provide/contract [#,create-id #,create-contract]))))]
    [(_ id field-info)
     #'(define-aug-struct (id #f) field-info)]))

and here's date-field.rkt:

#lang racket
(require "aug-struct.rkt")

(define-struct db-date
  (year month day))

(define-aug-struct date-field
  ([name string?]
   [contract any/c db-date?]))

So, it seems to be evaluating db-date? in the context of aug-struct
instead of of date-field.

I'm sure I'm just screwing something minor up that will prove my lack
of understanding of how macro expansion actually works. :-)

Thanks for looking!

On Tue, Jun 8, 2010 at 10:12 PM, Todd O'Bryan <toddobryan at gmail.com> wrote:
> Let me see if I can make a smaller example that does the same thing.
> The one I'm currently working on is a big mess...
> On Tue, Jun 8, 2010 at 9:07 PM, Ryan Culpepper <ryanc at ccs.neu.edu> wrote:
>> Can you post your macro and the example that produces the expansion you
>> included in your previous message? My earlier answer was based on a
>> conjecture about how your macro behaved, and it would be easier to provide a
>> complete answer if I can see exactly what you're doing.
>> Ryan
>> Todd O'Bryan wrote:
>>> It does, but I'm confused. Does that mean that any macro I define that
>>> includes something like a contract has to require every predicate it
>>> could conceivably want? How do I define a macro that will allow me to
>>> specify a predicate later, maybe even one that didn't exist when I
>>> wrote the macro? Or is that not possible?
>>> Or, and this kind of makes sense as I'm writing it, is there a way to
>>> tell a macro to wait to evaluate something in the location it's used,
>>> rather than in its own scope?
>>> Todd
>>> On Tue, Jun 8, 2010 at 5:57 PM, Ryan Culpepper <ryanc at ccs.neu.edu> wrote:
>>>> Todd O'Bryan wrote:
>>>>> I feel like I'm getting there, but macros still do things that confuse
>>>>> me.
>>>>> According to the Macro Stepper, I've written a macro that expands to:
>>>>> (module fields racket
>>>>>  (#%module-begin
>>>>>  (require "tables.rkt")
>>>>>  (require "../date-utils.rkt")
>>>>>  (begin
>>>>>    (define-struct
>>>>>     date-field
>>>>>     (contract)
>>>>>     #:transparent)
>>>>>    (define (create-date-field
>>>>>             #:contract
>>>>>             (contract db-date?))
>>>>>      (make-date-field contract))
>>>>>    (provide/contract
>>>>>     (create-date-field
>>>>>      (-> #:contract any/c date-field?))))))
>>>>> At the next step of the expansion, I get the error:
>>>>> expand: unbound identifier in module
>>>>> db-date?
>>>>> The only problem is that "../date-utils.rkt" provides db-date? and if
>>>>> I put everything inside the #%module-begin into its own DrRacket
>>>>> definitions window, it runs without error. For some reason, db-date?
>>>>> isn't available when it's needed, but I can't figure out how to make
>>>>> it available.
>>>>> What am I doing wrong?
>>>> Is your macro defined in "tables.rkt"? If that's the case, it doesn't
>>>> matter
>>>> whether the module where it's *used* imports date-utils, but whether the
>>>> module where the macro is *defined* imports date-utils. That's the idea
>>>> of
>>>> hygienic macros; they're lexically scoped.
>>>> You can use the macro stepper to check an identifier's binding by
>>>> clicking
>>>> on the identifier (here, the reference to 'db-date?'), then opening the
>>>> "Syntax properties" pane using the Stepper window. It'll tell you what
>>>> bindings, if any, the identifier refers to.
>>>> Did that help?
>>>> Ryan
-------------- next part --------------
A non-text attachment was scrubbed...
Name: aug-struct.zip
Type: application/zip
Size: 1646 bytes
Desc: not available
URL: <http://lists.racket-lang.org/users/archive/attachments/20100609/b6ba4fbb/attachment.zip>

Posted on the users mailing list.