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.

Reply via email to