Today, we will continue the little stroll around the Io language landscape that we started in Blame it on Io! A slow-paced introduction to the Io language. We will delve deeper into the central principle of prototype-based languages: cloning.
Cloning crash course
For the mad scientists and other evil dictators who landed here after googling “manual of cloning”, here is a crash course in object creation, à-la Io:
Movie := Object clone original := Movie clone original title := "Island of Lost Souls" original year := 1933 original scenario := "Mad scientist off-shores development of mutant race" remake := original clone remake title = "The Island of Dr Moreau" remake year = 1977 remakeOfRemake := remake clone remakeOfRemake year = 1996
Object creation in Io, like Polka, is a two step dance: 1. you clone, 2. you specialize.
As we have just seen, cloning an object is done by sending it the
clone message. This allows for a very elegant implementation of the Singleton pattern:
GeorgeClooney := FamousActor clone GeorgeClooney clone = GeorgeClooney
Bad news for my wife… but I digress.
To really understand cloning, it is necessary to look at how an object is implemented. I will spare you the details of the struct definition (see
vm/IoObject_struct.h if you really want to know) by remaining at a conceptual level. Io objects are made of two ingredients: slots and prototypes. Slots are stored in a hash table and represent both the data and the methods of the object. Prototypes are stored in a list and are the ancestors of the object — more on why this is a list in the next section.
How does this all fit together? Well, when a message is sent to an object, its name is used to query the slots hash table of the object. If no slot is found, the operation is repeated for each prototype in the prototypes list. When a matching slot is found, it is activated. If no slot is found, the
forward slot is activated. If no
forward slot is found, then you get an error. This will clarify:
JeanClaudeVanDamme := FamousActor clone JeanClaudeVanDamme act
Exception: Importer Object does not respond to 'act', whereas:
KeanuReeves := FamousActor clone KeanuReeves forward := method( writeln("I know ", call message name)) KeanuReeves kungfu
will even pretend that Keanu Reeves can act.
Now, back to our first question, what happens when an object is cloned? Very little actually: a new object is created with an empty slots hash table and a prototypes list containing the target of the
clone message. By default, a clone delegates everything to its original. Once it is created, a clone is usually specialized by adding slots in its slots hash table. The clone only stores the difference with its original. This is called differential inheritance.
Movie := Object clone Movie cast := List clone
would lead to all movies sharing the same cast! You could fix the problem by initializing the
cast slot to a freshly cloned list on every Movie clone, but this would be error-prone and very inelegant. This is where the
init slot comes into play. It is activated, if it is present, just after the creation of the clone. Let’s rewrite the previous piece of code:
Movie := Object clone Movie init := method(self cast := List clone)
self is required, otherwise the
cast slot will be local to the method.
This is the current state of affair in Io. But things might change, Steve Dekorte, the mad scientist behind Io, identifies 5 different cloning strategies with regards to slots:
- nil: the slot is set to
nilin the clone —
init := method(self foo = nil)
- shared: the slot is not created in the clone — the current default behaviour
- shallow: the slot is created with the original’s value —
init := method(self foo = foo)
- deep: the slot is created with a clone of the original’s value —
init := method(self foo = foo clone)
- very deep: the slot is created with a deep clone of the original’s value —
init := method(self foo = foo deepClone)
It is probable that support for each strategy will be added to Io at some point. For now, you have to use the
init slot to implement these strategies.
Dynamic inheritance with Protos
Let’s come back to the prototypes list. From what I have explained so far, it seems objects can have only one parent, the original. A list looks superfluous. Great news for my mad-scientists readers: the answer does not follow the laws of biology. Io makes it possible to add and remove parents to an object, at runtime, using
removeProto and other related methods. These methods simply manipulate the list of prototypes associated with an Io object.
LukeSkywalker := Person clone LukeSkywalker appendProto(Orphan) LukeSkywalker fightsWith(DarthVader) DarthVader tellsSecretTo(LukeSkywalker) LukeSkywalker removeProto(Orphan) LukeSkywalker appendProto(DarthVader)
And this concludes today’s stroll! I hope you found it informative and entertaining. I would like to thank everyone on the #io channel for your patience in dealing with my questions. Most, if not all, of the information contained in this post comes from chats on IRC and from the Io documentation. I’ll only claim the silly examples!