Seven Languages In Seven Weeks: Io - Day 1
With Ruby behind us, it's now time for Io - the next language in my Seven Languages in Seven Weeks book by Bruce Tate. Io is a prototype based language like Javascript; however, unlike Javascript, Io has no sense of classes that can be instantiated (I don't want to get into a debate as to whether or not Javascript actually has "classes"). Rather, everything is a clone of an existing object. And, when you clone an object, that cloned object becomes the new object's prototype.
Tate describes the Io language as being the closest thing to Object-Oriented LISP that you're likely to see. The syntax is very simple and revolves around the basic concept that you have objects and objects have slots that can contain either values or methods. But, you don't actually call methods "on" objects; rather, you pass objects "messages" and the objects can respond to those messages.
Ruby came pre-installed on my Mac; I was not so lucky with the Io language. It took me about an hour to get installed after I discovered Homebrew, the "missing package manager for OSX." I don't fully understand what Homebrew actually does; but, it seems to make installing non-commercial applications easier?
As Tate promised, the syntax, while odd at first, is pretty easy to learn; once I started playing with the interactive interpreter, dealing with lots of parenthesis started to feel quite natural. The biggest limitation of Io seems to be the lack of material out there. Typically, you can Google for something like "Io Langauge" and find a ton of stuff. Not so with Io. I found myself spending most of my time poking around the online documentation, which was actually pretty decent.
What do you say we rock out some homework? Heck yeah!!
HW1: Run an Io program from file.
Running an Io program from file is just like running a Ruby program from file. All you have to do is invoke the Io binary and pass it a file path:
io hw1.io
This will run the file, "hw1.io" and then exit. If the "io" binary is run without a file, it opens up the interactive Io interpreter.
Since this homework problem was rather straightforward, I decided to have some fun with it and try to put something juicy together. In the following scenario, I am creating two objects: Woman and BlackBook. I then clone the Woman object a bunch of times, place the clones in the BlackBook, and query the BlackBook for hotties:
- // Define the Woman class. This is a clone of the Object class.
- // Notice that "W" in woman is capitalized. This is a by-convetion
- // approach to creating Types.
- Woman := Object clone;
- // Define some object properites and setters. The ::= opreator sets
- // the default value AND creates setters for the value (in the form
- // of setNAME( value )).
- Woman name ::= "";
- Woman hair ::= "";
- Woman age ::= 0;
- // I am the constructor so that the clones of this woman can be
- // specialized.
- Woman init := method( name, hair, age,
- self setName( name );
- self setHair( hair );
- self setAge( age );
- // Return this object for method chaining.
- return( self );
- );
- // I say hello to the given person.
- Woman sayHello := method( toName,
- return (
- ("Hello #{toName}, my name is " .. self name()) interpolate
- );
- );
- // I return true if I am older than the given age.
- Woman olderThan := method( targetAge,
- return(
- self age() > targetAge
- );
- );
- // ---------------------------------------------------------- //
- // ---------------------------------------------------------- //
- // Define the BlackBook class. This will store a list of women
- // objects that can then be queried. Since women can be looked
- // up by name, we're going to clone the Map rather than Object.
- BlackBook := Map clone;
- // I am the class constructor. The black book can be initialized
- // with a list of lists.
- BlackBook init := method( women,
- return( self );
- );
- // I add a woman based on the given properties.
- BlackBook addWoman := method( name, hair, age,
- self atPut(
- name,
- Woman clone init( name, hair, age )
- );
- // Return this object reference for method chaining.
- return( self );
- );
- // I gather the hotties and return them in a list.
- BlackBook getHotties := method(
- // Only get the women who are old enough to be really hot.
- hotties := self select( name, woman,
- (woman age() > 28);
- );
- // Return hotties.
- return( hotties );
- );
- // I gather the hotties, but return them as a list.
- BlackBook getHottiesAsList := method(
- // Gather the hotties.
- hotties := self getHotties();
- // Create a list of sorted hotties.
- sortedHotties := list();
- // Sort the list of hottie names and then map the hotties
- // collection to the sorted list.
- hotties keys() sort() foreach( index, name,
- sortedHotties append(
- hotties at( name )
- );
- );
- // Return the sorted hotties list.
- return( sortedHotties );
- );
- // ---------------------------------------------------------- //
- // ---------------------------------------------------------- //
- // ---------------------------------------------------------- //
- // ---------------------------------------------------------- //
- // Create a new blackbook instance.
- blackBook := BlackBook clone init();
- // Add some women using data. The addWoman() method is a method that
- // we custom added to the cloned Map instance.
- blackBook addWoman( "Sarah", "brunette", 30 );
- blackBook addWoman( "Jill", "brunette", 28 );
- blackBook addWoman( "Cindi", "blonde", 22 );
- blackBook addWoman( "Alice", "red", 25 );
- blackBook addWoman( "Joanna", "brunette", 32 );
- // Add some women using existing instances. Since the blackbook is
- // clone of the Map object, we can use the native Map method like
- // atPut() to augment the blackbook collection.
- blackBook atPut(
- "Kim",
- Woman clone init( "Kim", "brunette", 35 )
- );
- blackBook atPut(
- "Amanda",
- Woman clone init( "Amanda", "brunette", 31 )
- );
- // ---------------------------------------------------------- //
- // ---------------------------------------------------------- //
- // Now that we have populated the black book, let's gather all of
- // the hotties form the collection.
- hotties := blackBook getHotties();
- // Output the hotties in random order.
- "Hotties in random order:" println;
- hotties foreach( name, woman,
- "... #{name} is smokin hot at #{woman age()}" interpolate println;
- );
- // Now, get the hotties again, this time in alphabetical order.
- hottiesList := blackBook getHottiesAsList();
- "Hotties in sorted order:" println;
- hottiesList foreach( index, woman,
- "... #{woman name()} is smokin hot at #{woman age()}" interpolate println;
- );
In this code, the Woman object is a clone of Object. BlackBook, on the other hand, is a clone of Map. When you clone an object, the cloned object becomes the prototype of the resultant object. As such, the Woman object can use the methods provided by Object and the BlackBook object can use the methods provided by Map (which, itself, can use the methods of Object). The Map object is like a Hash in Ruby or a Struct in ColdFusion and stores values by key.
Once I have created the BlackBook "instance" and populated it with Women, I then query the BlackBook for both a collection of hotties (Map) and a list of hotties (List). A List is like an array and stores values by order; Maps on the other hand, make no promise about the order of iteration.
When we run the above code, we get the following console output:
Hotties in random order:
... Kim is smokin hot at 35
... Joanna is smokin hot at 32
... Amanda is smokin hot at 31
... Sarah is smokin hot at 30
Hotties in sorted order:
... Amanda is smokin hot at 31
... Joanna is smokin hot at 32
... Kim is smokin hot at 35
... Sarah is smokin hot at 30
As you can see, the syntax for the Io language is pretty simple; and yet, it can do some very cool stuff!
HW2: Execute the code in a slot given its name.
- // Create a Katie object. Since our variable name starts with a
- // lowercase "k", it will not be considered a type. Meaning, the
- // type of this instance will be "Object", not "katie".
- katie := Object clone;
- // I am the instance name.
- katie name := "Katie";
- // I am the sayHello method.
- katie sayHello := method( name,
- return( "Hey there " .. name .. ", I'm " .. self name .. "." );
- );
- // I am the sayGoodbye method.
- katie sayGoodbye := method( name,
- return( "Gotta go " .. name .. ", catch you on the flip-side." );
- );
- // ---------------------------------------------------------- //
- // ---------------------------------------------------------- //
- // Loop over the list of method names.
- list( "sayHello", "sayGoodbye" ) foreach( index, methodName,
- // Execute the given method on Katie using perform().
- katie perform( methodName, "Ben" ) println;
- // Execute the given method on katie using doString().
- katie doString( methodName .. "( \"Ben\" )" ) println;
- // Get a refernece to the target method and then executes
- // it in the context of Katie instance.
- targetMethod := katie getSlot( methodName );
- katie targetMethod( "Ben" ) println
- );
This one took me a good bit of time to figure out since I basically had to read through the documentation of Object looking for ways to invoke slots (think: "keys", "members") dynamically. The perform() method seems to be the most readable. The getSlot() approach, on the other hand, is downright confusing at first. With getSlot(), I get a reference to the slot content without executing it. Then, I take that reference and execute it "on" the target object. What confuses me about this is that the intermediary variable, targetMethod, is not a property of the target object, katie. And yet, katie understands that the message represents the original slot value and executes it as such. I think this is where the power of "messaging" really comes into play - you're not calling methods on objects; rather, your passing them messages that they respond to.
When we run the above code, we get the following console output:
Hey there Ben, I'm Katie.
Hey there Ben, I'm Katie.
Hey there Ben, I'm Katie.
Gotta go Ben, catch you on the flip-side.
Gotta go Ben, catch you on the flip-side.
Gotta go Ben, catch you on the flip-side.
As you can see, all three approaches executed the code dynamically. And, furthermore, all three approaches were able to bind the method to the target object (which is where we get the "Katie" value in the sayHello() response).
I don't know if it's just the joy of looking at new languages, but the Io language seems to be fun to work with. I'm also really digging the fact that it shares some syntax with LISP. I learned a little bit of LISP for my Artificial Intelligence (AI) class in college and was completely baffled by it. The fact that I'm finding the Io language fun gives me hope that I'm at a point in my life where I can start to wrap my head around LISP (for when we get to the Clojure chapter).
Well, off to Thanksgiving - more when I get
Wow, that's a real neat and tidy language.
Thanks super mucho.
@John,
It's definitely a cute little language. I'm looking forward to the next two days. `do()` whose argument is executed in context of object on which it's being called:
The third thing, return's are implicit, so what ever the last line of a method was, its value will be returned:
Note the usage of `select()`, if you just give it one argument (a message), than that message will be sent to every object you're going through, instances of `Woman` in this case.
A bit off topic, but I'd be interested in a "Ben Nadel tames python" series as well :) When you get some time :)
@Josip,
I think the optionality of language syntax is highly personal. I come from a background in languages that don't have the option for things like semi-colons and parenthesis. As such, using them in this context actually allows me to visually parse the code faster as it conforms to something more familiar to me.
For example, I was just reading about message reflection and there are lines like this in the book:
call message arguments
Until you get really comfortable with the Io language way of doing this, I think this line is actually really hard to read. So, for something like that, I might end up writing it like this:
(call message()) arguments()
As someone who "gets" Io, you'd likely look at this line and find it absurd. And, I'm not saying that's wrong at all; it's simply how you are able to read the code at this point. I, on the other hand, am still getting comfortable with the concept of "everything is a message." As such, more explicit grouping and line delineation (ala semi-colons) helps me get more comfortable with the chain of message passing.
That said, thanks for the tip about do(). I had not see that and it looks cool. However, I wonder if this is somewhat akin to Javascript's "with()" block that allows an object to be pushed on the scope chain. I make this comparison because I believe that the with() block is being deprecated in Javascript and I believe will not allow code to pass JSLint tests. I assume the reason for this is that it actually cuts down on readability and perhaps creates for "unexpected" scope chain traversal.
As I look into each of the languages outlined in this book, I am trying to keep them as readable as possible in my mind. Readability, for me, is typically of the utmost importance. But, of course, as I said before, readability is also heavily influenced by how familiar you are with the language.
The explicit "return" of the last statement takes some getting used to. Thanks for the select() tip. See, now that is something that I think actually reads well... but probably only because I already have a few days of Io reading under my belt.
Thanks for the great feedback; I hope I am not being too critical. Like I said, I am coming from a very structure programming background, so the concept of "optional" feels very foreign :)
@Anthony,
That could be fun. If I recall, however, white-space in Python is actually very meaningful, right? That could definitely throw someone like me very far off :D You see how much I love me some white-space :)
Ben,
Comp 80, or did you take Schmolze's AI class?
@Joe,
I can't remember the class number, but it was Schmolze's AI class. We had to program LISP to simulate a robot moving through a maze... I was not able to complete the task. As such, I have a vendetta against the Clojure chapter :D
 shell-shocked by Krumme's evil Comp 40 ("Program this 'dog' to run around a field after a rabbit...in assembly! Using my own custom processor simulator and homebrew assembler! MWHAHAHAHA) that I wasn't sure I could handle another class like that.
Er....not "directly" .... meant "dynamically" .... need caffeine!
@Joe,
Agreed. It would be great if ColdFusion will one day support this kind of notation:
Object[ methodName ]( args... )
... That would make life at least 3 times more awesome :) I am sure someone has already put it in as an Enhancement Request for the next version of ColdFusion.
You can always do this:
evaluate( "object.#methodName#( args... )" )
... but having a more object-friendly approach, rather than a code-friendly approach would be sweet.
Also, writing anything in Assembly makes me unhappy :) I had to take a Assembly for my degree. We spent time rearranging lines of code to take advantage of NOOP commands; or rather, to make sure that we didn't need any NOOP commands... shudder.hesis." - which languages require semicolons?
JavaScript does not. ActionScript does not. Groovy, Scala, Io, Ruby all do not require semicolons.
Perl does (and heated discussions abound on that subject).
Adobe ColdFusion requires semicolons in cfscript (but Railo does not).
The more I work with a broader range of languages, the more annoying I find semicolons and I definitely have a preference for languages that don't make me write unnecessary punctuation :)
Nice to see the comment from Josip about idiomatic usage. Part of the point of Seven Languages, in my view, is to expose folks to different idioms and all seven featured languages are fairly low on ceremony (and punctuation).
@Sean,
I think it's awesome that Railo supports that kind of notation:
component[ method ]()
... I really hope Adobe ColdFusion supports it soon. It's really an excellent excellent excellent way to be able to invoke methods on components. And, we already have the other way working:
xmlSearch( ... )[ 1 ]
... so I have to ignorantly assume that going the other way isn't so much more work :)
Ok ok, I stand corrected about the semi-colon usage. Perhaps it was *me* that was requiring it, not the languages.
Going forward I will try to take advantage of the new language constructs.
Thx for posting you insight on IO. but your example is terrible. its sexist and you should replace it with a proper one!
@Stefon, it's a long-running "gag" on Ben's blog about his example code and he's blogged about it before (when the occasional person complains about it). It's part his "house style", you might say. He's not likely to change...
"?
@stefon, I'm not defending him, merely stating facts. You can see that even back in 2007, he was asking folks if/why they were offended and tried to curb his style:
This search shows how often he's been called on it:
That's what I meant about him not being likely to change.
Making sweeping assumptions about how his readers feel about women in general (and women in IT, in particular) is a false generalization.
 especially in the software industry. (e.g.:)
@stefon,
Thanx for the clarification. I'd read your "we" as being ascribed to me representing the readership of the blog, hence my objection.
Yes, the IT industry as a whole has a problem regarding both the percentage of women in the workplace and the treatment of them. The Microsoft story you linked to is awful! I remember the annual Hi-Fi show in London being similarly bad, but that was back in the 80's / 90's. I would have hoped, 20 years later, we would not still seeing that sort of thing in IT :(
On the positive side...
cf.Objective() held a Women In Technology event again in 2012:
One of my former colleagues organizes Ruby on Rails workshops for women:
I'd like to see a lot more of these types of events to encourage more women into IT. At the same time, I'd saddened that we even need this, and that IT is often so unwelcoming.
During my time at Macromedia, many of the best management teams were women, including the very best manager I've ever had in my (long) career. In general the percentage of women in IT there seemed one of the best I'd experienced. I don't know how things have fared under Adobe's yoke but I suspect things have regressed :(
But we are getting way off topic for a blog post on Io...