[plt-scheme] Implementation of define-struct

From: Ryan Culpepper (ryanc at ccs.neu.edu)
Date: Sun Jul 19 20:26:11 EDT 2009

Luís Fernando Schultz Xavier da Silveira wrote:
> I have a very simple (and probably stupid) question I can't figure
> out. Assuming the define-struct feature is not a core form, how is it
> it defines names based on a macro argument?
> 
> For instance, (define-struct point (x y)) generates point?, make-point,
> point-x and point-y. But how exactly is it I can create a macro that
> will define bindings whose names are derived from its arguments?

It cannot be done with just 'syntax-rules' macros. You need to be able 
to work with the syntax objects (the representation of programs, macro 
arguments, etc) directly. An introduction to general (or "procedural") 
macro transformers is here:

http://docs.plt-scheme.org/guide/proc-macros.html

--

Here's a similar, but simpler, macro that defines other names not given 
as arguments to the macro but based on an argument to the macro:

   ;; (define/get+set name expr) defines 'name' as a variable,
   ;; 'get-name' as a procedure that returns the variable's value,
   ;; and 'set-name!' as a procedure that updates the variable
   ;; with a new value.

First, this requires programming "for syntax", so:

   (require (for-syntax scheme/base))

Second, this task requires constructing new symbols, so here's a helpful 
function. It is a *compile-time* auxiliary function; if the 
'begin-for-syntax' were deleted, it would be a *run-time* function, and 
it couldn't be used inside of the macro. (See "Compile and Run-Time 
Phases" for more information.)

   (begin-for-syntax
     ;; format-symbol : format-string any ... -> symbol
     (define (format-symbol fmt . args)
       (string->symbol (apply format fmt args))))

Next thing is to write auxiliaries that construct the new identifiers 
you want to introduce. An identifier is just a syntax object containing 
a symbol; that gives you a way to create new ones based on old ones.

   (begin-for-syntax
     ;; make-getter : identifier -> identifier
     (define (make-getter name)
       (datum->syntax name (format-symbol "get-~a" (syntax-e name))))
     ;; make-setter : identifier -> identifier
     (define (make-setter name)
       (datum->syntax name (format-symbol "set-~a!" (syntax-e name)))))

Above, 'syntax-e' extracts the symbol. After the new symbol is 
constructed, 'datum->syntax' wraps it up again as a syntax object. There 
is important extra information that goes in a syntax object. In this 
case, 'datum->syntax' transfers that information from its first 
argument, the original identifier.

Here's the rest of the macro:

   (define-syntax (define/get+set stx)
     (syntax-case stx ()
       [(define/get+set name value)
        (with-syntax ([getter (make-getter #'name)]
                      [setter (make-setter #'name)])
          #'(begin (define name value)
                   (define (getter) name)
                   (define (setter new-value)
                     (set! name new-value))))]))

Try it out:

 > (define/get+set foo 12)
 > (get-foo)
12
 > (set-foo! 34)
 > (get-foo)
34
 > foo
34

Ryan


Posted on the users mailing list.