NOTE: This post was originally written in 2011 so it may be dated. I’m resurrecting it due to relative popularity. This post has been copied between several blogging systems (some of which were home-brewed) and some formatting has been lost along the way.

I recently started reading Seven Languages in Seven Weeks
by Bruce A. Tate and intended to ultimately post a review. Something went wrong
along the way, however. On the second language covered in the book I became so
intrigued that I had to dedicate a single post to it immediately. That language is Io,
a prototype-based language created by Steve Dekorte.

Now, you have to keep in mind that I’ve only been hacking Io for about three
days. I’m not an expert. I can’t promise any of the examples I’m going to provide
are idiomatic, robust or performant. I can’t promise any of my advice will be prudent or that this article can even properly tutor you on Io.

I can, however, make the strong recommendation that you give Io a shot especially if you’re
a novice JavaScript programmer. Being prototype-based and syntactically
simple Io can help an aspiring JavaScripter truly understand the patterns
without the baggage that comes along with JavaScript. I can only promise an honest attempt at whetting your appetite.

Here I’ll just outline some examples rather than deliver thorough instruction. For that Seven Languages in Seven Weeks
does a fine job (most of these examples are adapted from its exercises) and the Io Guide
can be a wonderful help as well.

Prototype-based

As I mentioned several times already Io is prototype-based. There are no classes
yet there are objects. Objects can clone other objects providing an inheritance
mechanism. Consider the following example.

Thing := Object clone

That code created a prototype named “Thing” from the super-prototype “Object”.
Note the “:=” assignment operator. That essentially creates the destination if it does not exist
yet. Had “Thing” already been assigned to “=” would have sufficed as it can only assign
an existing slot (slots are the named locations in an object where data members and methods can be stored).

Now lets add a method to the “Thing” prototype. The following code will add a
method into the “printMessage” slot of the “Thing” prototype.

Thing printMessage := method(
    writeln("Hello, thing!")
)

We can clone the “Thing” prototype into an instance and call the “printMessage”
as such.

thing := Thing clone
thing printMessage

which would output

Hello, thing!

Io thinks of things in terms of message-passing. Rather than saying, “we just
called thing’s printMessage method” we should say, “we just sent a printMessage
message to thing.”

Note that we cloned into a lower-case “thing”. When you’re defining a pure
prototype you start the identifier with a capital and instances with lower case.

Now lets add a data member to the instance and method to the prototype to
demonstrate parameters and encapsulation.

Thing calculatePrice := method(markup,
    self price + markup
)

thing price := 10
thing calculatePrice(2) println

which outputs

12

That example also demonstrates chaining messages together. calculatePrice
returned a Number and we in turn pass a println message to the number (as an
alternative to passing the number itself to writeln).

Metaprogramming

One of the exercises covered in Seven Languages in Seven Weeks that I found
interesting was changing the behavior of Io itself so that division by zero
would result in a 0 not infinity. Of course, you’d probably never want to do
that, but it’s a wonderful example of how much control you have over the
runtime itself.

For instance if you execute the following in Io without any additional
intervention

(6 / 0) println

you get

inf

Now let’s reach into Io and change how division works.

Number oldDiv := Number getSlot("/")

Number / := method(d,  
  if(d == 0, 0, self oldDiv(d))
)

(6 / 2) println
(6 / 0) println

outputting

3
0

The first output was done just to illustrate that we didn’t break division
entirely. The second illustrates that we sure did change how division by 0
works, rather than “inf” we got “0”.

Let’s break it down line by line.

Number oldDiv := Number getSlot("/")

That may look reasonable to a Rubyist who would “alias” in a situation like this. We’re basically copying the division operator’s (the method in slot “/”) logic into another slot named “oldDiv”. Since we’re going to rewrite “/”
we’ll want to keep the functionality around for later use and “oldDiv” is a fine
place.

Number / := method(d,  
  if(d == 0, 0, self oldDiv(d))
)

Now we’ve changed the “/” method of Number. If the denominator (the lone
parameter) is zero we will return zero. Otherwise we rely on the “oldDiv” to
perform normal division.

DSLs/Structured Data Parsing

As a Rubyist by trade I always chuckle when I hear .Net or Java programmers
claim that Domain Specific Languages are a waste of time. From their perspective
the time investment is far greater than any value that might be yielded. Nine
times out of ten they’re probably right. They work with tools with a fixed idea
of what instructions should look like.

Io gives you a great deal of control over the language’s parser itself. Rather
than writing a parser or implementing a DSL within the confines of the host language you can teach Io to parse and evaluate your DSL within its own interpreter! In Io the line between internal and external DSLs is somewhat blurred and I think that’s just fantastically nifty.

We’re going to write a little JSON parser here. Sure, there are probably better ways of parsing
JSON with Io but it provides an effective example that’s easy to relate to.

Take a file named “test.json” with the following content as given:

{
  "name": "Chris Umbel",
  "lucky_numbers": [6, 13],
  "job"\t: {
    "title": "Software Developer"
  }
}

The following code will parse the JSON data, albeit liberally. This is meant to
be demonstrative, not robust.

OperatorTable addAssignOperator(":", "atPutNumber")

curlyBrackets := method(
  data := Map clone
  call message arguments foreach(arg,
    data doMessage(arg))
  data
)

squareBrackets := method(
  arr := list()

  call message arguments foreach(arg,
    arr push(call sender doMessage(arg)))

  arr
)

Map atPutNumber := method(
  self atPut(
    # strip off leading and trailing quotes
    call evalArgAt(0) asMutable removePrefix("\"") removeSuffix("\""),
    call evalArgAt(1)
  )
)

s := File with("test.json") openForReading contents
json := doString(s)

json at("name") println
json at("lucky_numbers") println
json at("job") at("title") println

which will output

Chris Umbel
list(6, 13)
Software Developer

Now to break it down.

OperatorTable addAssignOperator(":", "atPutNumber")

Here we told Io to accept a brand spanking new assignment operator with the text
“:”. It will then pass the argument along to the target object via the
“atPutNumber” message.

curlyBrackets := method(
  data := Map clone
  call message arguments foreach(arg,
    data doMessage(arg))
  data
)

That instructs Io what to do when it comes across a curly bracket when parsing
code. In our case it it creates a Map and begins to fill it. “call” performs
reflection on the argument data passed to the method. “call message arguments”
accesses the list of all arguments recieved.

squareBrackets := method(
  arr := list()
  call message arguments foreach(arg,
    arr push(call sender doMessage(arg)))
  arr
)

Here we instructed Io how to deal with JSON arrays. Per the slot name it builds
a list of all elements enclosed in square brackets.

Map atPutNumber := method(
  self atPut(
    # strip off leading and trailing quotes
    call evalArgAt(0) asMutable removePrefix("\"") removeSuffix("\""),
    call evalArgAt(1)
  )
)

The Map prototype has been given a atPutNumber method that will strip quote off of the
element names and slap the value specified by the JSON into the corresponding
value in the Map. “evalArgAt” grabs the argument data at a specified index.

s := File with("test.json") openForReading contents
json := doString(s)

Now there’s the money. We loaded up the JSON document and slapped its contents in a
string. We then essentially eval it with “doString” letting the Io interpreter
do the dirty work.

Message Forwarding

In Ruby something called “method_missing” is relied upon to handle methods of
arbitrary names at runtime. The Io equivalent is “forward”. The following example
supplies a simple mechanism to build XML like:

<movies>
  <movie>
    <title>
      The Thing
    </title>
    <genre>
      Horror
    </genre>
  </movie>
</movies>

with code like:

builder := Builder clone
builder movies(
  movie(
    title("The Thing"),
    genre("Horror")
  )
)

Here’s the code to make it happen.

Builder := Object clone do(
  depth := 0
)

Builder indent := method(
  depth repeat(
    write("  ")
  )
)

Builder emit := method(
  indent

  call message arguments foreach(arg,
    write(call sender doMessage(arg))
  )

  writeln
)

Builder emitStart := method(
  emit("<", call evalArgs join, ">")
)

Builder emitEnd := method(text,
  emitStart("/", text)
)

# handles messages for non-existant methods. 
Builder forward := method(
  emitStart(call message name)
  depth = depth + 1

  call message arguments foreach(arg,
      content := self doMessage(arg)
      if(content type == "Sequence",
        emit(content)
      )
  )

  depth = depth - 1
  emitEnd(call message name)
)

builder := Builder clone
builder movies(
  movie(
    title("The Thing"),
    genre("Horror")
  )
)

Now that’s a big example, but let me extract the section of most relevance.

# handles messages for non-existant methods. 
Builder forward := method(
  emitStart(call message name)
  depth = depth + 1

  call message arguments foreach(arg,
      content := self doMessage(arg)
      if(content type == "Sequence",
        emit(content)
      )
  )

  depth = depth - 1
  emitEnd(call message name)
)

By implementing a method in the “forward” slot we allow the Builder prototype to
accept messages of any name. Our “forward” method then obtains the name of the message sent with
“call message name” and handles it accordingly (making a node of that name).

Next Steps

One thing not covered here is the concurrency story, and Io really shines
there. Easy coroutines, actors and futures are available to provide refreshing
simplicity to what’s typically a tricky problem.

There are also reasonable libraries available for common tasks like networking
and XML parsing.

Check out the Io Guide for
information on these and other topics. Of course Seven Languages in Seven Weeks
is wonderful as well.