Polymorphism and other important aspects of inheritance
Over on the Fediverse, I had a hot take:
My hot take on inheritance in programming languages is that it's a good thing to separate polymorphism, code reuse, and relationships between types. Also, the really important thing is polymorphism.
(We can see that polymorphism is the most important from how large C codebases keep re-inventing systems to create it.)
This was in fact too much of a hot take, because I left out something
that's sufficiently important that it keeps being reinvented. This
is what I'll call data inheritance or embedding. One version
of this embedding is done ad hoc in C through, for example,
preprocessor tricks or 'struct
at
start' literal embedding. Go has a more thorough version of C's
struct embedding (see Struct types
in the specification), where the embedded thing is more transparently
visible than you normally get in C, where fields and methods in the
embedded thing can be promoted to be accessed as if they were
part of the struct.
(You could argue that this falls under my 'relationship between types', but that's not what I actually meant by the phrase when I made my hot take.)
All of polymorphism, data inheritance, and generic method dispatching (often considered part of polymorphism) are important in practice to programmers because they allow different bits of code to be generic or narrowly specialized. In an operating system kernel, code for looking up file names can be generic across filesystems but delegate the question of 'is this name in this directory' to specific filesystems without having to know anything about them, and then allow the filesystems to return a relatively opaque reference to the name that supports a bunch of operations on it. These opaque references then support being put into various data structures used to implement efficient caches, which may involve things like having an embedded tree node in each of them, so that generic tree functions can work with them (efficiently, without separate allocations).
Not all languages directly support all aspects of these operations. Go's polymorphism is restricted to interfaces, which can only contain methods, not data. Python's duck typing allows data embedding, but doesn't surface the methods of embedded objects the way Go can, which has its own drawbacks.
As I said in my hot take, I do think it's important for a programming language to separate these aspects from classical inheritance itself, because there's a lot of times where you want one but not necessarily the other. Various modern languages demonstrate that this can be done in a type safe manner. I consider it unfortunate that older languages so closely tied these ideas together, often for reasons of implementation efficiency and simplicity.
|
|