Week 2: Io

Continuing the series I started last week, this post is about the entry in Seven Languages in Seven Weeks on the prototyping language Io. As before, I'm going to share my notes on this chapter, including a few excerpts, some significant methods, snippets of code, and links for further reading.

Week 1 ˙ Week 2 ˙ Week 3 ˙ Week 4 ˙ Week 5 ˙ Week 6 ˙ Week 7

Here is how Bruce Tate describes Io:

Names are sometimes deceiving, but you can tell a lot from Io. It's simultaneously reckless (ever try Googling for Io?) and brilliant. ... The language's syntax is simple and low-level, like the name. Io syntax simply chains messages together, with each message returning an object and each message taking optional parameters in parentheses. In Io, everything is a message that returns another receiver. ...
With Io, you won't worry about both classes and objects. You'll deal exclusively in objects, cloning them as needed. ... In a prototype language, every object is a clone of an existing object rather than a class. Io gets you about as close to object-oriented Lisp as you're likely to get.

Note that Seven Languages in Seven Weeks was published in 2010. Since then, .io has become a very popular TLD (top level domain)—including for the service that hosts this blog—as this article from 2013 points out. So the Googling hurdle is even higher than it used to be.

As the author points out, Io is a very straight-forward language with a simple syntax (but don't confuse simplicity for limited functionality). I felt like I was getting the hang of it faster than with Ruby. And it was fun to play around with.

After going through this chapter, my understanding of "prototyping" languages like Io is that they offer a sparse, trimmed-down take on object-oriented programming. By not dealing with classes, I found it easier to grasp the point of the core object-oriented approach of bundling related data and methods together in an object. As I state at the end of this post, I think Io could be a great language to use in education for that reason.

Hopefully, this post can provide a good explanation of objects in Io, starting with another quote from Bruce Tate:

This is how Io's object model works. Objects are just containers of slots. Get a slot by sending its name to an object. If the slot isn't there, Io calls the parent. That's all you have to understand. There are no classes or metaclasses. You don't have interfaces or modules. You just have objects.

Objects (which are named starting with a capital letter by convention) have slots (assigned by :=) which can contain data or a method. But if I understand correctly, slots can act as objects themselves (on a lower level) and objects can be seen as slots of the Lobby. Since slots have a name and contents, objects are basically structured like hashes (which comprise key–value pairs); in fact, you can call the asObject method on a hash object (a clone of Map) in Io to convert its contents to slots.

Typical syntax in Io would be something like Object slot message(arguments). Each word is a message that gets passed to the result of whatever has already been evaluated to its left. So in this case, slot gets passed to Object and identifies some data for example. Then message gets passed to that and acts on it. All objects are cloned from existing objects in a chain that eventually reaches back to the default Object and the Lobby. If a message is sent to an object and it is not the name of a slot on that object, then Io looks up the chain of that object's prototypes ("protos") until it finds a meaning for the message (this uses the forward method). This means that methods or data defined for an object are effectively inherited by anything cloned from it unless slots of the same name are defined for the clones.

I've alluded to some methods and objects in Io already, but this seems like a good place to list some examples that I think are interesting or useful:

  • Simply calling Lobby will show the contents of the Lobby (i.e. the highest-level objects). Calling the name of an object will show its slots.
  • Calling slotNames on an object will list the slots defined for it. protos lists its prototypes, and type shows its type (which is the name of the object itself if it starts with a capital letter).
  • The clone message is used to create a new object. Aside from the default Object, some useful things to clone are List and Map. For example, NewList := List clone creates an empty list called "NewList".
  • Useful methods to use on lists include: append(), size, at(), atPut().
  • method(instructions...) is used to define a method. It is often convenient to put a method in a slot.
  • for(index, start, end, statement) is typical loop syntax (while and repeat are other loops). continue, break, return are conditions that can be inserted into loops. if(condition, statement, alternative) is used for conditional evaluation.
  • The guide notes that there are 3 forms for using commands foreach, map, select. One form is foreach(variable, instructions).
  • Use a semi-colon to separate distinct messages without line breaks.
  • call and self are used for "reflecting" messages and objects, respectively. The former can give meta-information about a message (its sender, target, message name, message arguments, message argAt()) while the latter is a stand-in for the calling object.
  • To work with a file, you can call File with("filename.txt") openForReading contents. Similarly, URL with("url") fetch will get a website.
  • Finally, Io makes asyncronous/concurrent execution pretty user-friendly. I've never done any programming of this sort before, but I had no problem following most of the examples Bruce Tate gave—this is a credit to his writing as well as the clarity of the language. Preceding a message with @@ starts it in a separate thread (a single @ makes it a future, which will hold up the main thread if the result is called before it is available, if I understand correctly). yield temporarily suspends an asyncronous process, allowing another asyncronous process to advance (so two methods invoked starting with @@ would basically take turns if they contain regularly-spaced calls to yield).

Here are a couple of sample snippets of code from Io to illustrate what I've tried to explain. The first one is from Seven Languages in Seven Weeks, showing how to use OperatorTable addOperator to define an xor function.

Io> OperatorTable addOperator("xor", 11) //operator and precedence
==> OperatorTable_0x147b140:
Operators
  0   ? @ @@
  1   **
  2   % * /
  3   + -
  4   << >>
  5   < <= > >=
  6   != ==
  7   &
  8   ^
  9   |
  10  && and
  11  or xor ||
  12  ..
  13  %= &= *= += -= /= <<= >>= ^= |=
  14  return

Assign Operators
  ::= newSlot
  :=  setSlot
  =   updateSlot

To add a new operator: OperatorTable addOperator("+", 4) and implement the + message.
To add a new assign operator: OperatorTable addAssignOperator("=", "updateSlot") and implement the updateSlot message.

Io> true xor := method(bool, if(bool, false, true))
==> method(bool, 
    if(bool, false, true)
)
Io> false xor := method(bool, if(bool, true, false))
==> method(bool, 
    if(bool, true, false)
)
Io> true xor true
==> false
Io> true xor false
==> true
Io> false xor true
==> true
Io> false xor false
==> false

After inserting the new operator into the OperatorTable at the appropriate precedence position, it gets defined for true and false, then it is applied to check that it gives the expected results.

Another example in the book involved redefining forward (which is a method that exists in the lobby) as a short-cut to making an .xml builder: unknown messages would get printed as .xml syntax instead of passed to the prototype. I think the author likes this kind of hack, because he used .method_missing for a similar purpose in the previous chapter on Ruby.

This next example is one that I wrote for one of the exercises in the book. It calculates and prints the average of a list. Not very succintly, but it demonstrates working with lists and putting methods into slots (note that the methods are put in slots on List and because the list a was cloned from that object it has access to those methods):

a := List clone
a append(1,2,3,4,5)

a println

List mySum := method(tot := 0; foreach(item, tot = tot + item); tot)
List myAverage := method(self mySum / self size)

a myAverage println

Here are some links for further reading:

  • The official Io guide (which was a key reference for this post along with the chapter in Seven Languages in Seven Weeks) and tutorial (the "Dictionaries" section shows how to work with hashes—clones of Map).
  • There are some other introductory blog posts about this language: Chris Umbel's and Olivier Ansaldi's.
  • Wikibooks has some basic resources on Io such as how to Get Started and a style guide.

At the end of each chapter, Bruce Tate discusses strengths and weakness of the language. I feel like one area where Io has a lot of potential is in education. With just 3 or 4 hours over the course of a week, Io helped me gain a better understanding of some object-oriented programming concepts as well as concurrency.

The language in the next chapter is Prolog, which I'm a bit excited to dig into.

Permalink