[plt-scheme] An interface wrapper using macros and interface->method-names
On Jun 2, 2004, at 6:05 PM, David Stigant wrote:
> I've been trying to write a macro which will take an interface and
> create a class which forwards all calls to the interface's methods on
> to another implementation of that interface. For example, given this
> interface:
>
> (define ifoo<%> (interface () foo bar baz))
The above defines ifoo<%> to be a run-time value of kind interface.
> I want my macro
>
> (wrap ifoo<%>)
This is trying to use a value, which doesn't exist yet, to create a
piece of syntax. Once this piece of syntax is compiled and run will
produce a value that co-exists with ifoo<%>.
You can turn interfaces into quasi-syntactic things and simultaneously
create wrappers:
(require-for-syntax (lib "class.ss"))
(define-syntax (define-interface stx)
(syntax-case stx ()
[(_ iname (super-name ...) name ...)
(let ([iname-wraped
(datum->syntax-object
#'iname
(string->symbol
(string-append (symbol->string (syntax-e (syntax iname)))
"-wrapped")))])
#`(define-values (iname #,iname-wraped)
(let* ([ii (interface (super-name ...) name ...)]
[cc (class* object% (ii)
(super-new)
(init-field (implementation #f))
(define/public (switch-implementation i)
;; after some basic checks
(set! implementation i))
(define/public name (lambda (x) (send
implementation name x)))
...)])
(values ii cc))))]))
(define-interface ifoo () foo bar baz)
(define mumble (class object% (super-new) (define/public (foo x) 10)))
(define fake (new ifoo-wrapped (implementation (new mumble))))
(send fake foo 22)
; (send fake moo 33)
NOTE: I don't understand why the class* inside the macro doesn't signal
an error for not satisfying the interface.
-- Matthias
>
> to expand to this:
>
> (class* object% (ifoo<%>)
> (init-field (impl void))
> (public*
> (foo (lambda args (send impl foo . args)))
> (bar (lambda args (send impl bar . args)))
> (baz (lambda args (send impl baz . args)))
> (set-impl (lambda (i) (set! impl i))))
> (super-instantiate ())
>
> The first three methods of this class simply forward requests to the
> ifoo<%> interface onto impl. The set-impl method allows me to change
> the implementation under the covers without any of the clients of my
> object knowing or having to update their references to the new impl.
>
> Now, what I have started with is this:
>
> (define-syntax (wrap stx)
> (syntax-case stx ()
> ((wrap iface<%> (method ...))
> #`(class* object% (iface<%>)
> (init-field (impl void))
> (public*
> (set-impl (lambda (i) (set! impl i)))
> (method (lambda args (send impl method . args))) ...)
> (super-instantiate ())))))
>
> Which works great (ie it produces the correct class) except that I
> have to invoke the macro like this:
>
> (wrap ifoo<%> (foo bar baz))
>
> I don't want to have to list all the method names out everytime I use
> this wrapper. What if I change the method names in the interface?
> Now I have to go back and change all the invocations of the macro as
> well. Lousy. So I wanted to use the interface->method-names function
> to generate the list of methods and then pass those into the macro
> automatically. For reference, interface->method-names works like
> this:
>
> (interface->method-names ifoo<%>) ==> '(foo bar baz)
>
> So it seems like I ought to be able to do this:
>
> (wrap ifoo<%> (interface->method-names ifoo<%>))
>
>
> However, this yeilds a class with methods "interface->method-names"
> and "ifoo<%>" instead of "foo" "bar" and "baz". Or rather, doesn't
> produce anything since that class doesn't properly implement ifoo<%>
>
> What I've come up with is this hack:
>
> (define make-wrapper
> (lambda (iface<%>)
> (eval ` (wrap iface<%> ,(interface->method-names iface<%>)))))
>
> (define foo-wrapper% (make-wrapper ifoo<%>))
>
> But my skin crawls whenever I use eval. I've tried this out in local
> and global contexts and it seems to work, but is it really kosher and
> is there a cleaner way to accomplish what I want? Is there a way to
> combine the make-wrapper function into the macro definition?
>
> Any help is appreciated.
>
> Dave