Hello,
While doing some OOP in Racket today, I found myself in a situation that
would benefit from two seemingly contradictory things:
1.
I want to be able to override a superclass method, and I want to be
certain that I get to handle the method before any of my subclasses do.
This suggests I want to use inner.
2.
At the same time, I want my subclasses to be able to override this
method, not augment it. If I call inner and my subclass calls super,
control should jump to *my superclass*.
In other words, I want to get a sort of “first try” at handling the method
so that if I choose to, I can neglect to call my subclass’s implementation
altogether. But if I decide *not* to handle it, then I want super-style
dispatch to proceed as if my class were never there at all.
At first, I thought this wasn’t possible using Racket’s class system, since
if I override my superclass’s method using overment, the subclass
necessarily cannot use super, violating requirement 2. Yet if I use override,
I don’t get the “first try” I want, violating requirement 1. However, after
some thought, I realized it’s possible if I’m willing to use *two* classes
rather than one:
(define my-superclass%
(class object%
(define/public (m x) `(foo ,x))
(super-new)))
(define my-class%
(let ()
(define-local-member-name super-m)
(class (class my-superclass%
(define/public (super-m x)
(super m x))
(define/overment (m x)
(if (not x)
'skip
(inner (error "impossible") m x)))
(super-new))
(inherit super-m)
(define/augride (m x)
(super-m x))
(super-new))))
The trick here is twofold:
1.
First, I override m using overment, which ensures method dispatch will
call my implementation first.
2.
Next, I augment my own implementation of m using augride, which makes
the method overridable again in subclasses. To satisfy the other half of
requirement 2, my augmentation calls my-superclass%’s implementation of m
via a sort of secret “side channel,” kept private using
define-local-member-name.
Using this trick, subclasses of my-class% can still override m, and as long
as x is non-#f, my sneaky interposition doesn’t seem to have any effect.
But if x *is* #f, I can short-circuit the computation immediately:
(define my-subclass%
(class my-class%
(define/override (m x)
`(baz ,(super m x)))
(super-new)))
(define obj (new my-subclass%))
(send obj m #t) ; => '(baz (foo #t))
(send obj m #f) ; => 'skip
I think this is kind of cute, since it makes it possible to effectively
conditionally interpose on method dispatch. However, it’s rather awkward to
write. This brings me to my question: is there any simpler way to do this?
And are there any hidden gotchas to my technique?
Thanks,
Alexis
--
You received this message because you are subscribed to the Google Groups
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/racket-users/CAA8dsae1HR2CVUNq3aC4nc2vpPU6L9f-ENor-074LyZnWXgKYg%40mail.gmail.com.