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 racket-users+unsubscr...@googlegroups.com.
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