Prototypal inheritance misunderstood

Hey LtU, first post here :)

I have a question. I come from a mathematical background, so when I study something, I usually try to classify things in a more formal way than most. I was studying prototypal inheritance and was looking for a concise and correct definition this week. Not only is it hard to find proper descriptions that decouple prototype-based programming from JavaScript, it's even harder to find information on what makes the inheritance part tick.

The few that try to explain how prototypal anything works, get it blatantly wrong, confusing lots of different terms. One guy even called it synonimous to "the properties pattern", which was just ridiculous. Anyway...

From what I can tell, prototypal inheritance, which is at the core of languages like JavaScript, just means that it treats classes as first-class citizens, just like functional programming treats functions as first-class citizens. Of course, lots of small to big changes in how you design a language derive from that, giving shape to what what people see as prototype-based programming, but those are secondary, just like function-based programming usually gives rise to a "lambda syntax", which is also secondary.

Most often, here's what happens with prototypal inheritance:
- There's no explicit class type
- You can "inherit" from any object

What "inherit" means, depends on the language it seems, but the JavaScript library Stampit divides inheritance into a few behaviors, which looks like a clean way to describe it, to me. I haven't formalized it yet, should be a fun exercise. Basically, what I can see, so far:

- Creation (constructors, factory methods, etc)
- Delegation (parent objects, for example JavaScript __proto__, "classical" inheritance chains)
- Extension (copy/clone members, mixins, compare to jQuery extend for example)

JavaScript constructor functions ask you to provide a Creation function and use that to contruct a new object that is then added as a Delegation object. JavaScript's "Object.create" goes straight to delegation, the common "extend" pattern or any other kind of mixin pattern have Extension at their core. Classical inheritance usually forgoes Extension, working with mainly Creation and Delegation.

Feel free to provide more examples on how different languages use/combine these three and if these really cover the load. Thoughts on the "first-class classes" idea?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Luca Cardelli has done a lot

Luca Cardelli has done a lot of work studying prototype based objects formally. Run a few searches.

Great, thanks. I hadn't had

Great, thanks. I hadn't had the time yet to run through the relevant research papers to see if something came up.

Cardelli's research seems to

Cardelli's research seems to predate JavaScript as an important language, which is unfortunate, but from what I can see after quickly scanning through a few of his presentations, he does point to earlier research. Will keep everyone posted.

He seems to make the same subdivision in inheritance, but separates it from class-based, where I see a super-set or composition to abstract. He separates into Closures and Prototypes, Delegation and Embedding (what I call Extension). Interesting stuff for sure.

Many language have

Many language have 'first-class classes', eg. Python. One way of distinguishing between class-based and prototypical systems is that classes require a "new" primitive which takes a class as an argument and returns a fresh instance of that class. Some languages make this explicit, some make it implicit; for example, Python makes every class callable, so MyClass.__call__ is equivalent to new.

Once we have classes and instances we can model inheritance using the methods and properties of the Class-classes. For example, in a simple single-inheritance system (in Pseudo-Python):

class MyClassA(Object):
    pass

class MyClassB(MyClassA):
    pass

MyClassB.__class__ == Class, MyClassB.__parent__ == MyClassA, MyClassA.__class__ == Class, MyClassA.__parent__ == Object, Class.__class__ == Class, Class.__parent__ == Object, Object.__class__ == Class, Object.__parent__ == NULL

It's straightforward to bootstrap a circular model like this, eg. by leaving pointers dangling until their targets have been set up. Everything else is descended from these objects.

Prototypical languages don't need a "new" but they do need a "clone" primitive, which takes any object as an argument and produces a fresh object which inherits from the argument. In this way, the only relationship is inheritance; there's no instance-of. We can choose to implement a class hierarchy using prototypical objects if we like, but there's usually no need. For example, in psudo-Python:

MyObjectA = clone Object
MyObjectB = clone MyObjectA

MyObjectB.__parent__ == MyObjectA, MyObjectA.__parent__ == Object, Object.__parent__ == NULL

Objects don't delegate to their class, since there are no classes. Instead, objects delegate to their parents, in the same way that classes would do. Again, the implementation mechanism itself can be implemented in terms of the objects and their inheritance chains, but this time it's simpler since our bootstrap only needs to implement Object, everything else descends from that.

The difference between these systems is how they scope their properties. In class-based systems we look in the object itself, then in its class object, then in that class's parent object and so on until we hit the Object object.

In prototypical systems we look in the object itself, then in its parent object, and so on until we hit the Object object.

In this sense, prototypes basically do away with the distinction of classes and the indirection this causes. It also loses a load of inessential baggage which class-based systems all seem to copy from each other. For example, "constructors" are just functions which return fresh objects. In a prototypical language these are just regular functions, which happen to return fresh objects (eg. by calling clone or another constructor). In class-based systems there's usually a load of built-in machinery which distinguishes particular functions as being constructors and automatically calls them in particular situations.

I would take a look at Smalltalk and Self to see some interesting things being done with class-systems and prototypes. Unless you're specifically interested in their implementations, I'd steer clear of non-dynamic languages like C++, Java, etc. while trying to pin-down notions like class and prototype, since those languages tend to have *a lot* of associated baggage, and generally the implementations aren't metacircular (these are linked; for example in languages where classes aren't first-class, there is inevitably a need for "static" methods and properties).

Hey, thanks for the great

Hey, thanks for the great reply! By the way, I love prototypal inheritance, it's way better than classical OO systems imo, that might not have been clear in my post. I'm happy I put down some ground-rules and terms, so I can reply without spending a page on it :) First off, list of things for me to study (I do love studying): - Python classes, I've seen them come up a few times, but my last encounter with Python was over a decade ago - Self, but mostly Smalltalk. I have studied Self in the past, but Smalltalk is mostly foreign to me, except some notions of what it contains On to discussion:
Prototypical languages don't need a "new" but they do need a "clone" primitive
One thing about your argument that immediately confuses me, is that JavaScript, the most wide-spread prototypal language out there, doesn't have a clone primitive (might have gotten one recently though, haven't checked). Clone primitives are what I call Extension or what Luca Cardelli calls Embedding. JavaScript definitely only does Delegation when using either the constructor functions with the new operator or Object.create.
"Objects don't delegate to their class, since there are no classes. Instead, objects delegate to their parents, in the same way 
that classes would do. Again, the implementation mechanism itself can be implemented in terms of the objects and their inheritance chains, 
but this time it's simpler since our bootstrap only needs to implement Object, everything else descends from that.

The difference between these systems is how they scope their properties. In class-based systems we look in the object itself, then in its 
class object, then in that class's parent object and so on until we hit the Object object.

In prototypical systems we look in the object itself, then in its parent object, and so on until we hit the Object object."
This confuses me too. Here you step away from Extension, claim that prototypal is about Delegation. Then you go on to describe the difference between class-based inheritance and prototypal inheritance as one having a parent object and the other having a parent Class object. That's exactly the difference between first-class classes (prototypal) or no first-class classes (class-based). Maybe, your idea is that prototypal is exactly the absence of a class primitive? I can get behind that. Sounds reasonable to say that there's first-class classes as objects, probably through a primitive and that prototypal goes even further, and gets rid of the distinction entirely. In a similar vein, concatenative programming tends to get rid of the distinction between functions and objects entirely, even further than most functional languages. You also seem to bring in elements of Differential Inheritance, which is basically Delegation again. Thanks again for the reply, will have to reread it a few times.

Prototypical languages

Prototypical languages don't need a "new" but they do need a "clone" primitive, which takes any object as an argument and produces a fresh object which inherits from the argument.

My understanding is that clone in Self does a shallow clone of all of the properties of the prototype object. It doesn't delegate to it. (This is in contrast to Object.create() in JavaScript which does set up delegation.)

If clone did set up delegation in Self, you'd end up with increasingly long delegation chains as you made objects from objects from objects.

Instead, what happens in practice is that your prototype object has a parent slot pointing to a separate "traits" object. When the prototype gets cloned, that reference gets cloned too. So you end up with two objects (the prototype and the clone) that both delegate to the same object (the traits), and not the clone delegating to the prototype which delegates to the traits.

It also loses a load of inessential baggage which class-based systems all seem to copy from each other. For example, "constructors" are just functions which return fresh objects.

I'd argue that it doesn't remove that baggage, it just pushes it onto the language user. See Larry Wall's Waterbed Theory. :)

"new" and "clone"

Yes, that's right about "clone".

Slate took Self a step further, though, and re-introduced a method/protocol for "new" / factory patterns that used "clone" recursively, and were therefore more dynamic than static factory patterns that classes enable.

For example, in Smalltalk, one would have a Set and IdentitySet with hash and comparison methods specialized for equality or identity. In Slate, those would be parametric by slot values and the "new" pattern would just start from whatever receiver was called. Set and IdentitySet would merely be named prototypes for this.

This leads to a pattern where most methods do not use named prototypes but instead just implement their constructor methods by delegating to constructor methods of their parameters. In this way, global references are minimized, and code is very tightly scoped and easier to analyze (say, performing type inference).

Set and IdentitySet are toy examples, BTW, and extending this pattern worked pretty well across a number of areas.