The Heart & Soul of Prototypal OO: Concatenative Inheritance
What do jQuery, Underscore, Lodash, and the ES6 `Object` utilities have in common?
- They all contain a utility that serves the purpose of concatenative inheritance.
Concatenative inheritance is the most commonly used inheritance technique you’ve never heard of. Let me preempt some questions:
- Yes, it’s a thing.
- No, it’s not an obscure technique. Read on for citations of very common idioms and examples. You’ve probably used it 1,000 times and just didn’t know it’s called “concatenative inheritance”.
“Concatenative inheritance is the heart and soul of prototypal OO.”
What is Concatenative Inheritance?
Defaults/Overrides (>= ES5)
You can see examples of the ES5 defaults/overrides pattern in many thousands of jQuery plugins, where `$.extend()` originally gained popularity in between 2006–2010.
Of course, defaults/overrides is extremely common outside the jQuery world, as well. ES6 introduced parameter defaults, which is quickly replacing this pattern today.
This composition pattern started to gain mainstream use and popularity with Underscore in 2010, with wide adoption of the `Backbone.Events` mixin from the Backbone documentation:
Two Foundational Kinds of Prototypal Inheritance
In “What’s the Difference Between Class and Prototypal Inheritance?” I defined prototypal inheritance like this:
Prototypal Inheritance: A prototype is a working object instance. Objects inherit directly from other objects.
For those who’ve never heard of this, some background:
History of Concatenative Inheritance
Both copy and delegate mechanisms are recognized in the often-cited “Treaty of Orlando” which describes different approaches to object-oriented code reuse:
Empathy: The ability of one object to share the behavior of another object without explicit redefinition.
Template: The ability to create a new object based on a template, a “cookie-cutter” which guarantees, at least in part, characteristics of the newly created object.
[The summary expands on templates:] Templates allow two objects to share a common form. They may be embedded inside classes, or they may be objects themselves.
A little translation is needed here. Essentially, they’re saying that the primary mechanisms of inheritance are:
- The ability to “share the behavior”, or delegate property lookup from the target object to some already existing object properties, aka prototype delegation.
- The ability to create new instances based on pre-defined behavior, as in the ability to share behavior by creating a non-referenced copy of the behavior which then becomes instance safe.
In class-based environments, you can use the delegate pattern from “Design Patterns: Elements of Reusable Object Oriented Software” (for delegation), and classes (for templates) to achieve these aims.
- Empathy is accomplished with delegate prototypes.
- Templates are accomplished by copying from existing objects, sometimes called exemplar prototypes.
You can also dynamically extend an existing object, which leads us down the road toward concatenation as an inheritance mechanism…
The Treaty of Orlando describes what we now refer to as dynamic object extension in the section on “minimal templates”:
“…a minimal template is a cookie cutter in the same sense, but once created, cookie-cut objects can define other attributes as well. An extended instance — one generated by a minimal template, then added to — does not, a priori, have a template for its type. It’s descendants cannot be strict instances, since there is a no template for their type.”
This describes the idea that objects can be built by adding properties to existing instances ad-hoc: Dynamic object extension, which is the foundation of concatenative inheritance.
“Dynamic object extension […] the foundation of concatenative inheritance.”
In the book “The Interpretation of Object Oriented Languages” by Iain Craig (if you hunt this down, I recommend the first edition — later editions introduced lots of confusing typographic errors) describes concatenative inheritance used in combination with delegates in prototype-based Kevo language, which has become the canonical language reference for the origins of the phrase, “concatenative inheritance”:
“Every Kevo object can be considered a complete set of all the properties associated with its parent objects. This means that Kevo objects are entirely self-contained and no parent relation is required to express inter-object derivation relationships.”
“Kevo objects […] can be created anew by the specification of slots and methods. Alternatively, they can be created by cloning and modification.”
The Self Connection
- Composition of Multiple Ancestors
- Dynamic object extension
“The design of inheritance and encapsulation in Self, an object-oriented language based on prototypes, results from understanding that inheritance allows parents to be shared parts of their children.”
Concatenative Inheritance: The Most Commonly Used Inheritance You’ve Never Heard Of
People have developed a very strong association between the word “inheritance” and “class inheritance” — so much so that they don’t recognize any other form of inheritance: even if it’s more commonly used.
Concatenative inheritance is so common and so simple, people do it every day without even realizing it.
See the proliferation of facilitating utilities in all of the most-frequently used utility libraries: `jQuery.extend()`, `underscore.extend()`, `lodash.assign()`, and now the built-in `Object.assign()` for evidence of widespread use.
Keep an Open Mind About Inheritance
If you arbitrarily restrict your understanding of language terms too much to a narrow definition, you often blind yourself to a more broadly usable understanding which can be effectively applied to a wider range of problems, and often to much simpler solutions to your existing problem.
He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.