[plt-scheme] Implementation of define-struct
From: Luís Fernando Schultz Xavier da Silveira (lfsxs0 at gmail.com)
Date: Sun Jul 19 22:44:31 EDT 2009 |
|
Thank you very, very much for the help. It was really useful and I
really appreciated it.
On Sun, Jul 19, 2009 at 08:26:11PM -0400, Ryan Culpepper wrote:
> 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