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.
Differential inheritance
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
will raise: 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.
Clones sharing memories with their original, not only makes bad hollywood movies (The Island and Replicant), it is not always desirable:
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)
Note that self
is required, otherwise the cast
slot will be local to the method.
Future directions
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
nil
in 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 appendProto
, prependProto
, 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!
Wednesday, March 29th, 2006 at 23:01
Objects meeting up in the lobby: Io
Recently, a cool new programming language called Io has been getting some well deserved attention. Behind the name, which sounds like a fundamental system library, there’s an exciting language. Io is prototype based, like Javascript and Self, eve…
Tuesday, April 4th, 2006 at 3:55
Great Stuff. Just finding out about io and these two posts have been super.
dru
Tuesday, April 4th, 2006 at 9:16
Thanks for the feedback, dru!
A quick update: a couple of people have joined the #io channel following these two posts. Woohoo! I am proud to see that my blog is contributing to this community.