November 4, 2012
Prototypes in Io language 0
For the last couple of days, I have been going through the “Seven Languages in Seven Weeks” book, by Bruce Tate. The second language of the book is Io, a programming language unknown to me before I picked up the book.
One of the challenges of the book, was to create a prototype for a two-dimensional list, and implement a method to return the transpose of the list. We will see how this can be very elegant under Io’s prototype based paradigm.
In Io, everything is an object, and you can interact with the objects by sending messages that correspond to slot names. Here is an example:
TwoDimList := Object clone |
In this simple line of code, we send the message clone to the root-level object Object which returns a new object, which we assign to TwoDimList. Objects are simply collection of slots, that can hold other objects (a method is an object as well). Lets send a message to our newly created object, to check its slots:
TwoDimList slotNames ==> list(type) |
As you can see, we have one slot named type. One might wonder, at this point, where did the slotNames message came from. Our TwoDimList object does not have a slot with that name, so he just forwards the message to the prototype from which he was created. If we send the type message, to both Object and TwoDimList, we get the following:
Io> TwoDimList type ==> TwoDimList Io> Object type ==> Object |
So, in practice, we have created a new type, by using the clone call. Lets make another:
twodimlist := TwoDimList clone ==> TwoDimList_0x263f160 |
Now, we have created another Object as before, but there is a subtle difference:
Io> twodimlist slotNames ==> list() Io> twodimlist type ==> TwoDimList |
So, our twodimlist has no type slot, which means that it will forward the message to the prototype from which he was created. That is how we distinguish new types from instances of types in Io. By convention, if an Object begins with a capital letter, then Io will assume it is a new type, and add a type with its name. Otherwise, it does not.
Now that we have created a new type for our two-dimensional list, and an instance of it, we need to add some behaviour to it. In other words, we need to add slots with methods that allow us to create a two-dimensional list, and set/get values within it.
Fortunately, Io already provides a List prototype that we can use. We will consider a two-dimensional list to be an Io primitive list of lists. Here is the code:
TwoDimList dim := method(x,y, self dimx := x self dimy := y self matrix := List clone for(i, 0, x-1, 1, row := List clone for(j, 0, y-1, 1, row append(nil)) matrix append(row)) return matrix) TwoDimList set := method(x,y,value, row := matrix at(x-1) row atPut(y-1,value)) TwoDimList get := method(x,y, row := matrix at(x-1) return row at(y-1)) TwoDimList print := method( for(i, 1, dimx, 1, for(j, 1, dimy, 1, value := self get(i,j) "#{value} " interpolate print) "" println)) |
Here is where Io starts getting really powerful: we are adding new slots to an object, at runtime, which will allow us to do some sophisticated programming.
The dim method will take two parameters, corresponding to the list dimensions, and store them into two new slots of the TwoDimList object (dimx and dimy). Our two-dimensional list will be stored in the matrix slot, and the two for loops will initialize all values to nil.
The set and get methods are also pretty simple. The set will receive two coordinates and a value to store there, while the get will retrieve the value at those coordinates. I am also considering that my two-dimensional list has coordinates starting in “1″, while the primitive list has coordinates starting in “0″.
For this exercise, I have also implemented a print method that will go through the list and print all the values, with a new line each time we change to another sub-list. Therefore, the following code:
twodimlist dim(3,4) twodimlist set(1,1,1) twodimlist set(1,2,2) twodimlist set(1,3,3) twodimlist set(1,4,4) twodimlist set(2,1,5) twodimlist set(2,2,6) twodimlist set(2,3,7) twodimlist set(2,4,8) twodimlist set(3,1,9) twodimlist set(3,2,10) twodimlist set(3,3,11) twodimlist set(3,4,12) twodimlist print |
Creates the following output:
1 2 3 4 5 6 7 8 9 10 11 12 |
Now, how do we create a transpose of a two-dimensional list? From wikipedia, we know that the transpose of a matrix has the rows of the original matrix as its columns. So, we do:
TwoDimList createTranspose := method( transposed := self clone transposed matrix := self matrix clone transposed dimx := self dimy transposed dimy := self dimx transposed regularGet := self getSlot("get") transposed get := method(x,y, self regularGet(y,x)) transposed set := method(x,y,value, row := self matrix at(y-1) clone row atPut(x-1,value) matrix atPut(y-1,row)) transposed) |
Now, this is where we begin to really lift the power of Io slots and prototypes paradigm. We begin by creating a new object, as before, by cloning the existing one. Now, we know that the transpose will have its dimensions reversed, compared to the original two-dimensional list, and we assign them as such.
As the transposed matrix has the rows of the original matrix as its columns, we simply override the get slot, in order to reverse the order of the arguments! We do this by cloning the original method into the regularGet slot, and store a new method on the get slot that invokes it, with arguments reversed! At this point, at least for reading purposes, we have just created an abstraction that “creates” a transposed matrix in O(1) time, simply by redefining slots.
Now, when it comes to the set method, things get a little tricky: at first, I tried to simply invoke the set method, with arguments reversed, but there is catch: as we haven’t actually cloned the sub-lists inside the matrix, so the set modified the existing matrix, resulting in a leak of our abstraction. So, I had to guarantee that the sub-list was also cloned, and put into the transposed matrix, leaving the original one intact.
Lets try to create, and print our transposed instance:
transposedlist := twodimlist createTranspose transposedlist print 1 5 9 2 6 10 3 7 11 4 8 12 transposedlist set(4,3,13) transposedlist print 1 5 9 2 6 10 3 7 11 4 8 13 twodimlist print 1 2 3 4 5 6 7 8 9 10 11 12 |
As our print slot relies on the get slot to retrieve the values, no changes to it are necessary! I am looking forward to learn even more.

