Sovereign Agentic AI (Volodymyrs View) is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.
Nobody in Star Wars ever says “Luke, I am your father.” The real line is “No, I am your father.” Millions of people misremember it, and they all misremember it the same way, with total confidence.
JavaScript’s family tree gets the same treatment. Ask a room of developers where JavaScript objects come from and you will hear “Java” (that was a marketing deal), “C” (the curly braces), or “Brendan Eich, in ten days” (true, but he didn’t invent them from nothing).
The real answer is a language most of them have never seen: Self, designed in 1986 by David Ungar and Randall B. Smith at Xerox PARC. Self never had a large user base. Its ideas did. Every JavaScript object you have ever created follows Self’s model, and the engine running your code is built on tricks the Self team invented to make that model fast.
Three ideas instead of a class hierarchy
Self is Smalltalk’s child. It kept the messages, the blocks and the live world of objects. Then it started deleting things.
Smalltalk said everything is an object, including classes. That honesty produced a tower: every class has a metaclass, every metaclass is an instance of Metaclass, and so on until the staircase loops back on itself. Ungar and Smith looked at that and asked what classes actually cost. Two concepts where one would do. No way to make a single one-off object. Instance variables read one way from inside and another way from outside. And every time a class changed, every existing instance had to be converted to the new shape.
Their 1987 paper, Self: The Power of Simplicity, kept just three ideas:
- 1.
An object is a set of named slots. A slot holds a value or a method, and reading one looks exactly like calling the other.
- 2.
New objects are copies. No classes, no constructors. You build an example object, the prototype, and copy it.
- 3.
What you don’t know, ask your parent. A message an object can’t answer is delegated to its parent slots, and
selfstays bound to the original receiver.
Here is a father and a son in Self:
lobby _AddSlots: (|
father = (| parent* = traits clonable.
name <- 'Self'.
reveal = ( name, ': I am your father.' ) |).
son = (| parent* <- father. name <- 'JavaScript' |)
|).
son reveal "=> 'JavaScript: I am your father.'"The son has no reveal method, so he asks his father. The father’s method runs, but self is still the son, so it speaks in the son’s name. If you write JavaScript, you already know this shape: it is Object.create, the prototype chain and this.
Two smaller details matter more than they look. First, every access is a message, even from inside the object, so you can swap a stored value for a computed one without anyone noticing. That is the uniform access principle; JavaScript only got it with getters in ES5. Second, parent slots are ordinary slots, so an object can change who it inherits from while the program runs.
The revolution was in the engine room
By 1986 standards, Self was a performance disaster on paper. Reading a variable was a message send. Every if was a message send with blocks. There were no classes for a compiler to reason about, and any object could grow slots or change parents at any moment. Critics said it could never be fast.
The Self group, first at Stanford and then at Sun, spent eight years proving them wrong:
Maps (1989). Objects copied from the same prototype share a hidden structure recording which slot sits where. Underneath, the VM keeps something very like a class. The program never sees it.
Customised compilation (1989). A separate compiled version of each method per receiver kind, so sends to
selfcan be resolved early and inlined.Polymorphic inline caches (1991). Each call site remembers the few receiver types it has actually seen and jumps straight to the right method. As a side effect, it records which types really occur.
Dynamic deoptimisation (1992). When you open a debugger or change a method the optimised code depended on, the VM turns optimised frames back into simple ones. Aggressive optimisation and a fully live, debuggable system at the same time.
Type feedback and adaptive recompilation (1994). Start with a fast, simple compiler, watch the program run, and recompile the hot code for the types the caches actually saw.
The real revolution was not prototypes; others had them first. It was a reversal of who adapts to whom. Before Self, language designers bent the language to suit the compiler: declare your types, freeze your classes. Self kept the language simple and dynamic and made the virtual machine learn the program while it ran. Speculate, optimise, and back out when wrong.
Then the people carried the ideas forward. Self’s VM team built Strongtalk at Animorphic Systems. Sun bought Animorphic in 1997, and that work became Java HotSpot. Lars Bak, who worked on Self, Strongtalk and HotSpot, went on to lead V8 at Google in 2008. Today that lineage runs in Chrome, Node.js, Deno and Electron. V8 still calls its hidden classes Maps, the same word the Self papers used.
How we drifted away from the original idea
In May 1995 Brendan Eich had about ten days. He wanted Scheme; Netscape wanted something that looked like Java. He shipped Java’s syntax, Scheme’s first-class functions and Self’s objects. Put the two side by side and they are the same program:
const father = {
name: 'Self',
reveal() { return this.name + ': I am your father.'; }
};
const son = Object.create(father); // parent* = father
son.name = 'JavaScript';
son.reveal(); // 'JavaScript: I am your father.'The son even improved on the father in one place. In Self, assigning to a slot you don’t own walks up and overwrites the parent’s slot, so a child that tries to rename itself renames its father. JavaScript creates a new own property instead. That was a good call.
Everything after that is a story of hiding the father.
To look respectable to Java programmers, JavaScript added new and constructor functions. Every function got a prototype property, which led to two decades of developers confusing prototype with __proto__. The plain, Self-style way to make an object with a given parent, Object.create, only became standard in 2009, after Douglas Crockford argued for it for years.
In 2015 the son put on the helmet: the class keyword. But a class is still a function, extends still builds a prototype chain, and an instance still owns nothing and borrows everything. The costume brought back the fragile base class problem in its familiar form, which is why JavaScript later added #private fields. Meanwhile React moved its recommended style from class components to plain functions in 2019. The son put the helmet on, then slowly took it off.
The saddest twist is that the father’s two gifts now fight each other inside the son. Changing an object’s parent at run time is a Self feature. Hidden classes are a Self optimisation. Combine them in JavaScript and the optimisation punishes you for using the feature: MDN’s page for Object.setPrototypeOf opens with a warning that it is very slow in every engine. We kept Self’s speed tricks and quietly abandoned the dynamism they were invented to make affordable.
Why this matters if you write code for a living
This is not nostalgia. Knowing the father explains things you already deal with every week.
Performance folklore stops being folklore. “Initialise properties in the same order.” “Don’t add fields after construction.” “Keep call sites monomorphic.” “Never call setPrototypeOf on hot objects.” Each of these is advice about Self’s maps and inline caches. Once you know what a map is, you stop memorising rules and start predicting behaviour.
You can reason about class honestly. JavaScript classes are a costume over delegation. When this behaves strangely, when a mixin leaks, when a method borrowed from a prototype speaks in the wrong voice, you are looking at Self’s semantics, not Java’s.
The fragile base class problem has two halves. Prototypes remove the structural half: children don’t embed their parent’s layout, and nobody reaches into anybody’s fields, so a parent can change its storage without breaking a child. They don’t remove the behavioural half: if a parent calls its own methods through self and a child overrides one, behaviour changes in ways neither author intended. That isn’t a bug in prototypes. It’s late binding, the very thing that makes objects worth having.
The design lesson still holds. Keep the language simple and let the implementation be clever. Concrete examples beat abstract descriptions, which is exactly how component-and-instance design tools work today. And one way in makes change cheap: when everything is a message, you can change how something works without anyone noticing.
Self failed as a product and won three times as a set of ideas: its objects are in every browser, its VM techniques are in HotSpot and V8, and its live, grab-anything Morphic environment lives on in Squeak, Pharo and every “edit and see it instantly” tool.
Self, alive again, in Rust
Reading about Self is one thing. Poking it is another. So I rewrote Self in Rust: a fully working, open-source implementation compiled to WebAssembly, so it runs in any browser with nothing to install.
It comes with three ways in:
The mini book, No, I Am Your Father. Nine short chapters, about 40 minutes, from Smalltalk’s metaclass tower to JavaScript’s
classhelmet. Every Self example is live: edit it, press Run, and it executes on the real engine. The JavaScript examples run in your browser’s own engine right next to them.The playground. A blank page with every example from the book, for your own experiments.
The Self World desktop. Outliners for every object, a shell, and Morphic windows built from Self objects, the way Self was meant to be used.
The interpreter is deliberately straightforward. It has none of the original VM’s tricks, so it is slower than the real Self was in 1995. That is part of the point: you can feel exactly what maps and inline caches were invented to fix.
One detail I can’t resist. If you open the book in Chrome, the WebAssembly is compiled by V8, a grandchild of the Self VM. You are running Self inside its own grandson.
So the next time someone tells you JavaScript is Java’s little brother, correct them, and get the quote right while you’re at it: “No. Self is your father.”
Read the book: volland.github.io/self-lang-rs/book
Try the playground: volland.github.io/self-lang-rs/play
Open the desktop: volland.github.io/self-lang-rs/try
Star the code: github.com/Volland/self-lang-rs