Developer

Introducing Io, a prototype-based language

Chad Perrin has learned a thing or two about programming from an interesting language called Io. He thinks you might have something to learn from it, too.

These days, when someone says "I'm a programmer," that typically means the person does object-oriented programming. Some programmers do more functional programming than object-oriented programming, perhaps, but even they typically do object-oriented programming as well. What "object-oriented programming" means to most developers involves creating classes, filling them with instance methods, then instantiating objects from these classes, and sending messages to the object instances that tell the object to execute its methods — which were defined in the class.

Other details can differ. In general, your object-oriented language of choice probably uses dot notation for sending messages and parentheses for passing arguments with those messages:

object.message(argument)

There are exceptions, however. For instance, sending a message looks somewhat different in Perl with its "arrow" dereferencing notation:

object->message(argument)

In Ruby, parentheses are often optional for message arguments:

object.message argument

For the most widespread object-oriented languages, this kind of syntactic ephemera is the biggest difference between the approach they take to the most basic operations in those languages. It requires delving quite a bit deeper to find more substantial differences, such as Ruby's almost pathologically open classes and use of mixins as a way to provide more than single inheritance without running afoul of issues like the diamond inheritance problem.

Prototype-based languages differ somewhat more fundamentally from class based object-oriented languages. In prototype-based languages such as Io and JavaScript, you do not instantiate a class to get an object; instead, you clone a different object, as in this Io example:

io> Greeting := Object clone

==> Greeting_0x2855c580:

type = "Greeting"

This gives you a simple object called Greeting that has essentially the same properties as the object called Object, which is the root of all inheritance in Io.

Note that these examples will use the notation used in Io's REPL, an interactive interpreter that works something like a normal command shell except that its behavior is defined by the Io language rather than by a more typical shell language. The Io REPL's display uses an Io prompt, Io>, to show it is ready to take input, and an arrow prompt, ==>, to show that what follows is the return value of the code entered at the preceding Io prompt. Actual program output for a given line of code will appear between the Io prompt and its following arrow prompt; additional status information will appear between the arrow prompt and the next Io prompt.

Each of these objects has a number of "slots" that it inherits from its immediate ancestor. Additional slots can be added:

io> Greeting description := "a way to say hello"

==> a way to say hello

io> Greeting speak := method("Hello, world!" println)

==> method(

"Hello, world!" println

)

Slots can contain values or methods. The description slot in this example contains a value, while the speak slot contains a method. To get the return value of either — whether the return value is the value stored in a slot that contains a value, or the value produced by executing a method:

io> Greeting description

==> a way to say hello

Io> Greeting speak

Hello, world!

==> Hello, world!

If the relevant slot is not defined for a given object, Io walks up its family tree to see if any ancestors have that slot when it is referenced in code. In this example, Farewell has no description or speak slots of its own:

Io> Farewell := Greeting clone

==> Farewell_0x283ac9a0:

type = "Farewell"

Io> Farewell description

==> a way to say hello

Io> Farewell speak

Hello, world!

==> Hello, world!

That can be changed, though:

Io> Farewell description := "a way to say goodbye"

==> a way to say goodbye

Io> Farewell speak := method("Goodbye, world!" println)

==> method(

"Goodbye, world!" println

)

Io> Farewell description

==> a way to say goodbye

Io> Farewell speak

Goodbye, world!

==> Goodbye, world!

So far, you have only seen the initialization assignment operator, :=, which must be used whenever assigning a value to a slot that does not already exist. For slots that do already exist, the = assignment operator will suffice:

Io> Farewell description = "a way to say adios"

==> a way to say adios

Since Io has no classes, no object has a class; instead, it has a type and a prototype. The type is itself, if its name is capitalized, which identifies it to Io as a type. The prototype is the definition for its immediate ancestor. An object that starts with a lower case letter is an instance of a type, and not itself a type:

Io> emoFarewell := Farewell clone

==> Farewell_0x283ac9a0:

Io> emoFarewell speak := "Goodbye, crule world!"

==> Goodbye, crule world!

Io> emoFarewell type

==> Farewell

Io> emoFarewell proto

==> Farewell_0x283ac9a0:

description = "a way to say adios"

speak = method(...)

type = "Farewell"

Io> Farewell type

==> Farewell

This does not mean you cannot clone an object that is not a type; it does, however, mean that the new clone is considered an instance of the prototype of what it clones, rather than an instance of the non-type object it clones directly:

Io> happyFarewell := emoFarewell

==> Farewell_0x283ac9a0:

speak = "Goodbye, crule world!"

Io> happyFarewell speak := method("Good riddance, world!" println)

==> method(

"Good riddance, world!" println

)

Io> happyFarewell type

==> Farewell

Io> happyFarewell proto

==> Farewell_0x283afb80:

description = "a way to say adios"

speak = method(...)

type = "Farewell"

Methods have types and prototypes as well because, unlike in Ruby, they too are objects in Io:

Io> clone type

==> Object

Io> Farewell clone type

==> Farewell

This, in essence, explains the basics of what it means to be a prototype-based language — specifically the prototype based language Io. Other languages will differ in details, but the concepts are largely the same. Just as someone might recommend you learn a "pure" functional programming language such as Haskell to expand your understanding of programming concepts if you are only familiar with procedural and object-oriented languages, learning a prototype based object-oriented programming language is a good way to expand your understanding of concepts if the only object-oriented programming languages you have previously encountered are class based.

JavaScript is probably the prototype-based language you are most likely to encounter, and aside from being prototype based, it tends to be much more similar to mainstream class-based object-oriented programming languages than Io. This might make it easier to understand, all else being equal, but for purposes of learning new things and expanding your knowledge of programming, it also makes the journey less exciting and informative. Perhaps worse, it can be difficult to find reasonable excuses to write JavaScript code more than a few lines long because of the language's most common context: the Webpage.

Io's most common implementation and use is better suited to general purpose programming than JavaScript's typical implementation, the Web browser's JavaScript engine. It is easier to find good reasons to write bigger programs where you will get more opportunity to gain a deeper understanding of how a prototype language works. Your humble author has written little snippets of JavaScript for more than a decade without ever really getting a feel for how different a prototype-based language is from a class-based language, but after spending a few days with Io, I have a much firmer grasp of the basic differences.

In general, Io is a very simple, small language that offers some very flexible, powerful approaches to getting things done. It also lacks a lot of the syntactic sugar you might expect from familiarity with other languages. The result is at times you will find that you can get a lot done with very little code without sacrificing a lot of readability; and other times, you will find that you need to implement what you have come to regard as basic functionality yourself.

Io also incorporates a number of syntactic design decisions that you may find annoying or tedious to deal with, such as the fact that an interpolate message must be sent to a string to get it to interpolate variables, and those variables must be enclosed in special #{ } delimiters, essentially doubling up on the special tokens that must be used for delimiters. Meanwhile, a single quote string type that does not even interpolate the escape character for a newline does not exist in Io; instead, you need to use three times as many double quotes as for a "normal" Io string.

Using Io is a remarkably interesting experience, in any case. Its creator started creating the language as a way to gain better understanding of interpreter design. You might start using the language as a way to gain better understanding of programming in general.

Io> coderBenediction := Farewell clone

==> Farewell_0x283afb80:

Io> coderBenediction speak := method("Go forth and code!" println)

==> method(

"Go forth and code!" println

)

Io> coderBenediction speak

Go forth and code!

==> Go forth and code!

Installation note: If your operating system does not have the standard, copyfree Io implementation available through its software management system, the installer for the BSD licensed interpreter — along with more information about the language — can be found through the official Io site.

About

Chad Perrin is an IT consultant, developer, and freelance professional writer. He holds both Microsoft and CompTIA certifications and is a graduate of two IT industry trade schools.

Editor's Picks

Free Newsletters, In your Inbox