Jimmy Koppel Profile picture
Turning good engineers into great at https://t.co/b5295anmRU . Reverse engineer. Blogs about software design at https://t.co/RYL1GZ92df. Ph. D. in programming languages from @MIT.

Mar 8, 2023, 23 tweets

"The Flaws of Inheritance" by @CodeAesthetic1 is beautiful, as always.

Problem though, is that none of the things discussed in the video have anything to do with inheritance

Time for a 🧵 on the most mind-bending construction in mainstream programming languages

@CodeAesthetic1 Some context: I was talking to Norman Ramsey a few months ago about his new book. We started talking about objects, and he told me he barely covers them. Why? "Objects are not an undergrad topic"

Inheritance is probably the hardest part of the theory of objects.

If I say "X doesn't understand inheritance," read it in the same tone as "X doesn't understand quantum Chu algebras."

Here's how the late William Cook defined it

Back to CodeAesthetic.

He says that the greatest downfall of inheritance is that, if you want to add a new variant that implements some methods but not others, you need to change the base class and many consumers.

That has nothing to do with inheritance.

Rather, that's a problem of putting too much stuff in an interface

Haskell doesn't have classes, but you can have this problem by putting too much stuff in a typeclass

#JuliaLang really doesn't have classes, but you can write code assuming everything which supports + also has *

Funny enough, the video's example of composition actually isn't

Composition requires that an object have another object as a field, but the "composed" version here just takes an image as a param. It's providing a helper function, not extending an image with new functionality

Something that throws people is that classes in mainstream programming languages actually combine 8 different concepts into one.

It's important to distinguish these concepts, as some of them come with opposite advice. We're told to avoid instanceof for objects, but if your objects actually encode a sum type, then it's required.

This conflation kills the video's attempted classifaciton.

Both entries in the Inheritance row are wrong.

First, trying to compare "parent classes" with "interfaces" does not typecheck.

That's like saying "Use an Aeron instead of a chair."

Interfaces are a record mapping method names to types, together with properties they should satisfy

Classes are implementations

It works this way in type theory, but it also works this way in major languages as they're actually used, where people use IWidget instead of Widget

If you only refer to values through their interfaces, then this becomes non-distinguishing.

You can inherit from a class all you want and wind up with a new implementation of the same interface. You can toss in any combination of new interfaces you want.

The "extending part" is also wrong.

What? Isn't that what inheritance is all about.

Nope. That's subtyping.

In fact, usual presentations of composition also are just extending.

The prototypical example of composition is a hash table that also counts the number of times put() was called. Same operations, plus a getCount. Composition can (should?) be extending as well!

Similarly, you extend an interface just by throwing on a couple more methods. No inheritance in sight. This is also present in languages that don't support objects, like Haskell and its typeclasses.

So, what is inheritance?

That's a 90-minute lesson I give to select alumni of my course ( mirdin.com/the-advanced-s… ), but here's the short version:

Inheritance is creating a new implementation from old

For it to be more than subtyping, you must override existing methods!

Nothing in the video's example code does that!

That's why the problem could be fixed by putting save/load on an interface outside the hierarchy.

If you squint right, the "inheritance" example code is actually a better example of composition than the "composition" example code!

So what's the real lesson of composition over inheritance?

It can't be about adding new functions, as that's just subtyping. If you implement that with composition vs. inheritance, the code winds up almost the same. Trivially interconvertible, no maintenance difference.

Instead it's about overriding existing implementations.

Overriding is scary. Every caller is suddenly doing something else.

For the counting hash table example, if you override put() to increment a counter, then it will also get incremented during a rehash. Or maybe not. That's an implementation detail. Can change on a whim

The composition version calls into the original code instead, and is robust

So that's what it means to favor composition over inheritance. Overriding existing implementations is dangerous

So when do you need to? When is true inheritance really required

That's a profound question which solves a major puzzle of software engineering.

It also has a short, simple answer

When there is bidirectional dependence between the base and child class.

That's it. That's what true inheritance is for. When composition fails.

But most uses of the "extends" keyword in mainstream languages are just subtyping and are a lot more like composition. So keep it up

Share this Scrolly Tale with your friends.

A Scrolly Tale is a new way to read Twitter threads with a more visually immersive experience.
Discover more beautiful Scrolly Tales like this.

Keep scrolling