Software Development

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.

34 comments
cory.schultze
cory.schultze

At least with bloaty, object-orientated languages, there is a spell-check function. If "Cruel" were a verb/method/operator your program would certainly not work! I will openly admit, I'm a bit of a novice to programming but this language looks to be a nightmare to debug! Whilst Class-based languagess tend to be simple and structured, Prototype-based languages are closer to the root and, reinforcing your point, clearly teach the programmer better coding by delving into the depths of referencing and object inheritance. But I thought the idea of class-based languages was to make coding quicker and simpler? Please put me right if I don't seem to have got the point - I don't doubt there are others like me who could benefit from this, if only I could see the benefit.

Tony Hopkinson
Tony Hopkinson

to think of something that form of description would lend itself to though, otherwise my ingrained approach will kick in and I'll be fighting it, a lot.... Neural net or generic algorithms, something off the well beaten track anyway.

Sterling chip Camden
Sterling chip Camden

Thanks for that. Actually, in Ruby methods are objects, too -- it just may not be all that obvious. For instance, you can get the method object for any object's method using the method method. Confusing enough? Here's an example: x = "abc" y = x.method :to_s puts y.call the above prints "abc", because it's invoking the to_s method of x.

apotheon
apotheon

The misspelling, "crule", was intentional and ironic. As for the rest -- I'm having fun watching the other threads develop.

Justin James
Justin James

Look inside the code for a C#, VB.NET, Java, or C++ class... what do you see? Procedural code. J.Ja

Tony Hopkinson
Tony Hopkinson

based OO languages make describing a common set of solutions in a certain way potentially simpler. If you used a straight procedural language to implement a solution you'd normally do with OO, certain things would be harder, others would be easier. A classic is the static class. You create a class and put some general methods in it. Say read and write to registry keys, and call it from everywhere, the only reason it's a class is because you can't have anything in the language that isn't one and you didn't want to repeated add that code to every class that needed registry access. You don't have to do that in non-class based OO languages. There again if you want to keep all the things related to CustomerOrder in one place and get some reuse, you've got a lot of problems... So the language becomes a design constraint, it forces you to express yourself in a particular way. Whether that's simpler or quicker, depends on what you want to express. One of the main things you get out of learning a different language is it forces you to express yourself in a different way, you learn another way to say it, and that way may be more powerful in different scenarios. Use one language and you end up in rut, every problem looks like a nail and you reach for the hammer. Like when some eejit implements a database in a worksheet.... You learn to separate what programming is from the tool (language) you use to express it.

apotheon
apotheon

I do not really foresee a whole lot of heavy Io development in the future, given its capabilities compared with other languages -- but it is definitely worth learning (and using a little) as a way to expand my experience, knowledge, and mind. Tackling a whole new type of software development might be a good way to kill two birds with one stone when it comes to expanding your mind as a programmer.

apotheon
apotheon

Read the docs; methods are not objects. The "method object" is an object you can get from a method, but is not the method itself.

apotheon
apotheon

Objective Caml . . . ? I don't think I'll bring up CommonLisp. It may have an object system that is, by all accounts, "powerful" -- but that's far from the language's main feature or defining development paradigm.

Sterling chip Camden
Sterling chip Camden

As revolutionary as OO seemed to us back in the late 80s and early 90s, it's not all that different from procedural programming -- when you consider the difference between both and functional programming. OOP turns out to be a syntactical expansion of the basic concepts of procedural programming. Even in Fortran and COBOL you have objects, it's just that you can't formally define your own classes -- you have to take those that the language gives you, and build your own only by convention or in your conceptual framework. But it's still objects doing things to and with other objects. Functional Programming turns that around and focuses on the definition of processes. Objects become secondary. The "how" is more important than the "what".

Sterling chip Camden
Sterling chip Camden

So the language becomes a design constraint, it forces you to express yourself in a particular way. Whether that's simpler or quicker, depends on what you want to express. That's a nicely balanced view. Unfortunately, I'm starting to find the classic OO idiom, which used to be my lingua cogitatum, has recently become about as relevant as Chaucer.

Justin James
Justin James

It seems like one of those languages that you play with, maybe implement some common functionality libraries in (quick sort, tree walks, etc.) and learn a lot about being a better programmer along the way. J.Ja

AnsuGisalas
AnsuGisalas

is that derivation I was wondering about a while back.

Sterling chip Camden
Sterling chip Camden

... that seems like more of an implementation detail than a feature of the language. After all, you can call it just like any other proc object.

Sterling chip Camden
Sterling chip Camden

I think that's a symptom of the correctness of its design. It doesn't have to force its design down the programmer's throat because it's just right. Ruby shares that quality with Lisp, though the two languages come at it from different directions. Lisp starts functionally, but naturally includes objects. Ruby starts with objects, but naturally adopts the functional paradigm. Lisp's correctness is mathematical, Ruby's is linguistic.

apotheon
apotheon

My appreciation of Ruby's stylistic flexibility just keeps growing. It's a bit surprising to see that a consistently applied, universal support for a particular development paradigm is so easily leveraged to employ the techniques of other paradigms pretty much seamlessly.

Sterling chip Camden
Sterling chip Camden

CLOS makes OOP functional indeed. Haskell's syntax, too. Even Ruby when done well employs objects as the nouns on which the functional verbs operate, without resorting to an imperative style (and without having to create a subject for every verb).

Justin James
Justin James

It's possible, with a little syntactic sugar, to make an OO language look identical to a procedural language. In fact, Ruby does it by having a semi-invisible "global object" which is always in scope, with a top of static methods so that as an OO language, you can write 100% procedural code if that's what you want to do. I know you know this, by the way, just spelling it out for those who don't... J.Ja

apotheon
apotheon

It sounds like you should use more lexical closures (or monads, perhaps, depending on the language). Of course, in Ruby, I think I'd recommend instantiating objects at least as often as you use proc objects (which act a heck of a lot like standard lexical closures in other languages, of course) -- but if you're mostly using objects for state management, it kinda sounds like you should be using Common Lisp instead. Maybe I'm mistaken. I might be jumping to conclusions about what's going on, or something. Feel free to ignore this if it's way off-base.

Sterling chip Camden
Sterling chip Camden

I still use OO, of course -- I just don't start my conceptual model there. I'm learning to think in terms of the functional model, and only bring in objects as needed for state management. Methods naturally collect around those bits of state on which they operate, but the driving force is the stateless function. Not that I've "arrived" by any means, and it depends a lot on the language I'm using. In a language like Ruby, it works very well -- while in C#/Java it's mostly wishful thinking.

apotheon
apotheon

How do you find your style changing such that OO program construction is becoming roughly irrelevant? I'm curious.

Tony Hopkinson
Tony Hopkinson

But the number of hoops you have to jump through to do something the way you want to can get irritating. Possibility of Ruby soon at our place, might suggest some places where maybe I can persuade the powers that be, that IronRuby modules might be a better choice than C# in the current code. Maybe F# ones in places, we already use IronPython for somethings, but that was designed pre DLR, so there's some scope to rework a rather complex means of interfacing the two. Pre-DLR it was just too slow, near everything had to be done with refection. Too much on my plate at the moment to have a look unfortunately.

apotheon
apotheon

As long as the syntax is consistent and clear, I'm okay with it. That means I'm as comfortable with either of these: 'one two three'.split.each {|e| puts e } (loop for x in (string-split " " "one two three") do (print x)) Defining string-split is left as an exercise for the reader.

Sterling chip Camden
Sterling chip Camden

... which is something that has recently started to bother me about the OOP . syntax. In Lisp, even using CLOS (OOP), you'd say (run bob) Even though run could end up being a method of bob's class, if run is defined as a generic function. Putting the noun first just seems wrong, except that it does allow for chaining to read left-to-right.

apotheon
apotheon

Actually, Sterling's Ruby and Io examples are the opposite of the way English works, because they both have imperative syntax. One isn't saying "Bob runs" with a bob.run message in Ruby: one is, instead, saying "Run, Bob!" While the run in my example is the name of a method that the Bob object can execute, the actual run in the code there is not the method itself; it is a message sent to Bob, telling Bob to execute the run method. See, we have a class, Person: class Person def initialize(name) @name = name end def run puts "#{@name} goes really fast!" end end What you see there, in the short block of code that starts def run, is the method. Here we have instantiation of an object from that class and a method call: bob = Person.new('Robert') bob.run => Robert goes really fast! In this case, run is just a message sent to the Bob object. It's like when I say "Run, Bob!" which is a message I give him; when he starts running, the act of running is the method he employs for responding to that message. Thus, bob.run is backwards, because you aren't saying "Bob runs." You're saying "Run, Bob."

AnsuGisalas
AnsuGisalas

You didn't have to remove it though: Repetition is the mother of readiness!

apotheon
apotheon

edit: Never mind, I just repeated at great length what you say later in the thread with AnsuGisalas.

AnsuGisalas
AnsuGisalas

I wonder if the basque see that differently, then ;) Ergative pseudo-english : "Bob killed with an axe, by his wife" actually newspaper headline english comes close at times... bob being the undergoer and his wife being the agent of the action.

Sterling chip Camden
Sterling chip Camden

You could pass the "object" as parameters: you.beat(fred) But more typically and confusingly, the object is often treated as the actor, as in: my_string.reverse That's almost reflexive -- but we tend to think of the program as the implied second-person actor. In those cases, parameters tend to act as prepositional objects: my_string.split(' ') which reads, "split my_string on space".

AnsuGisalas
AnsuGisalas

is for NVO However, is computer syntax usually of only two segments? (=intransitive) That would be fitting for the normal structure of an "instruction", being imperative. English imperatives are verb-first though, as I suspect is the universal tendency (excepting yoda-speak). I'll check the universals though. EDIT: So far the findings are meek, but one thing is for a tendency for the agent of the imperative to be left out (since it's usually the addressee) - this of course can't apply to a computer programming instruction, as it's a third-person imperative. They have those in Finnish "Jumala heid├Ąt auttakoon" > "God them help-imp.3rd" ~ God help them (which is an english example of the same, but without identifying morphology).

Sterling chip Camden
Sterling chip Camden

After re-reading Chad's article, I see that Io is noun first after all, juts omitting the familiar dot. By way of comparison: Io: Object method Ruby: object.method Lisp: (generic-function object)

Sterling chip Camden
Sterling chip Camden

Few object-oriented languages use a verb-first notation. That's much more functional, like Lisp's CLOS (in that respect). I'll hjave to dig deeper to see what, if anything, that does to differentiate the language in practical terms.

apotheon
apotheon

A proc object is not a method. It is an object that takes the call method. It's a characteristic of the language, rather than a "mere" implementation detail. It's just kind of a fiddly, academic characteristic of the language.

Editor's Picks