The following is a brief discussion of classes and the issues with subclassing. It was originally written with the intention of explaining thr trouble to some of my office-mates. I’ve become more interested in a reasonable resolution to the dynamic dispatch problem (which, to be accurate, has two well-defined solutions that I find unsatisfactory). I’ve recently read the Classboxes proposal, which I find grossly unsatisfactory (after partially implementing the semantics, but that’s another post), and I decided to look into the problem.

First, let’s suppose we’re creating objects in a language like Racket. We can start by defining a small object that has a subobject:

```
(struct self1 (odd even))
(define obj1
(letrec
((odd (lambda (x) (if (zero? x) #f (even (sub1 x)))))
(even (lambda (x) (if (zero? x) #t (odd (sub1 x))))))
(self1 odd even)))
(define subobj1
(self1 (lambda (x) #t) (self1-even obj1)))
```

The first defines a set of functions and constructs an object that
has the two functions (called *methods*) that are mutually recursive. The
sub-object defines a new object that inherits `even`

from `obj1`

but
redefines `odd`

to always return true. What happens if we invoke these
functions?

Let’s consider this:

```
> ((self1-odd obj1) 5)
#t
> ((self1-even obj1) 5)
#f
> ((self1-even subobj1) 5)
#f
> ((self1-odd subobj1) 5)
#t
```

That worked as we hoped! But why? Because we’ve cleverly made odd look “the same” in this example and only fed it odd numbers. Let’s consider another call:

```
> ((self1-odd subobj1) 4)
#t
```

Let’s try defining a new define another subclass:

```
(define subobj2
(self1 (lambda (x) 5) (self1-even obj1)))
```

Let’s try the same calls:

```
> ((self1-odd subobj2) 5)
5
> ((self1-even subobj2) 5)
#f
```

Why did that work? Well, ‘work’? Because of Lexical Scope: because we’ve decided to close over odd in our first definition of even. But objects don’t really work this way. Let’s add self!

```
(struct self2 (odd even))
(define obj2
(letrec
((odd (lambda (self x) (if (zero? x) #f ((self2-even self) self (sub1 x)))))
(even (lambda (self x) (if (zero? x) #f ((self2-odd self) self (sub1 x))))))
(self2 odd even)))
```

This is how it works in “modern languages”. Now let’s see our subclassing:

```
(define subobj3
(self2 (lambda (self x) 5) (self2-even obj2)))
```

Now, some more calls:

```
> ((self2-odd subobj3) subobj3 7)
5
> ((self2-even subobj3) subobj3 7)
5
```

And boy is that a problem! We’re relying on Dynamic Scope to do what we’d like here. What’s worse, we could imagine that odd is a private function, which means a subclasses might shatter security:

```
(struct self3 (private-key encrypt encrypt-with-private-key))
(define secure-obj
(letrec
((key 11)
(encrypt (lambda (self x key) (expt x key)))
(encrypt-with-private-key
(lambda (self msg) ((self3-encrypt self) self msg key))))
(self3 key encrypt encrypt-with-private-key)))
```

Now we can imagine encrypt and key are private here, right? And safely call
`encrypt-with-private-key`

:

```
> ((self3-encrypt-with-private-key secure-obj) secure-obj 3)
177147
```

But a subclass will allow me to only rewrite anything I please, and scope it the way I want! Note I am calling getters here, but that’s what any subclassing compiler is going to do, anyway.

```
(define secure-subobj
(self3
(self3-private-key secure-obj)
(lambda (self x key) key)
(self3-encrypt-with-private-key secure-obj)))
```

Now I can try to encrypt again!

```
> ((self3-encrypt-with-private-key secure-subobj) secure-subobj 3)
11
```

While this isn’t really ‘a serious error’ in some sense, what have we reconstructed? The ability to arbitrarily perform superclass reflection via subclassing! And in a language where functions are first-class, I might even capture private methods defined on a class using a subclass’s definition in this model.

Okay, so this has been problematic, because of Dynamic Scope. Let’s try it the other way, so that we maintain lexical scoping.

```
(define obj3
(letrec
((key 11)
(encrypt (lambda (x key) (expt x key)))
(encrypt-with-private-key
(lambda (msg) ((self3-encrypt self) msg key)))
(self (self3 key encrypt encrypt-with-private-key)))
self))
```

Self is no longer exposed to the user, and our subclass utterly fails to be tricky:

```
(define subobj4
(self3
(self3-private-key obj3)
(lambda (x key) key)
(self3-encrypt-with-private-key obj3)))
> ((self3-encrypt-with-private-key subobj4) 3)
177147
```

There’s our safty back! We can no longer change the underlying definition of any method or field in the superclass using this model.

But what have we lost? Local self-reference!

```
(define subobj5
(self3
13
(self3-encrypt obj3)
(self3-encrypt-with-private-key obj3)))
```

Dang, now we can’t change our keys out!

```
> ((self3-encrypt-with-private-key subobj5) 3)
177147
> ((self3-encrypt-with-private-key obj3) 3)
177147
```

So it’s not actually enough to specify things how we’ve done: neither lexical
*or* dynamic scope is quite what we want. We’d like to choose ones with some escape
mechanism, which is at least partially-described in Dan Friedman’s
Object-Oriented Style.
Even so, the paper provides the following discussion of scopeis:
“This decision is quite arbitrary, but any reasonable characterization of
which variables shadow which over variables can be [easily implemented].”

Most modern language pick one solution (like explicitly requiring `super`

, or
implicity shadowing things). Unfortunately, there is significant trade-off in
every case, and I can’t help but think there’s a better solution…