3.3 Day 2: The Sausage King

Think back to Ferris Bueller for a moment. In the movie, the middle-class high-school student represented himself as the sausage king of Chicago in a classic bluff. He got a great table in a great restaurant because he was willing to bend the rules. If you’re coming from a Java background and you liked it, you’re thinking about what could have happened—too much freedom is not always a good thing. Bueller probably deserved to be thrown out. In Io, you’re going to need to relax a little and take advantage of the power. If you’re coming from a Perl scripting background, you probably liked Bueller’s bluff because of the result of the bluff. If you’ve traditionally played things a little fast and loose, you’re going to have to pull back a little and inject some discipline. In day 2, you’ll start to see how you might use Io’s slots and messages to shape core behaviors.

Conditionals and Loops

All of Io’s conditional statements are implemented without syntactical sugar. You’ll find they are easy to understand and remember, but they’re a little harder to read because of fewer syntactic clues. Setting up a simple infinite loop is easy. Type Control+C to break out:

  Io> loop("getting dizzy..." println)
  getting dizzy...
  getting dizzy...
  ...
  getting dizzy.^C
  IoVM:
  Received signal. Setting interrupt flag.
  ...

Loops will often be useful with the various concurrency constructs, but you’ll normally want to choose one of the conditional looping constructs, such as a while loop. A while loop takes a condition and a message to evaluate. Keep in mind that a semicolon concatenates two distinct messages:

  Io> i := 1
  ==> 1
  Io> while(i <= 11, i println; i = i + 1); "This one goes up to 11" println
  1
  2
  ...
  10
  11
  This one goes up to 11

You could do the same with a for loop. The for loop takes the name of the counter, the first value, the last value, an optional increment, and a message with sender.

  Io> for(i, 1, 11, i println); "This one goes up to 11" println
  1
  2
  ...
  10
  11
  This one goes up to 11
  ==> This one goes up to 11

And with the optional increment:

  Io> for(i, 1, 11, 2, i println); "This one goes up to 11" println
  1
  3
  5
  7
  9
  11
  This one goes up to 11
  ==> This one goes up to 11

In fact, you can often have an arbitrary number of parameters. Did you catch that the optional parameter is the fourth one? Io will allow you to attach extra parameters. That may seem to be convenient, but you need to watch carefully because there is no compiler to babysit you:

  Io> for(i, 1, 2, 1, i println, "extra argument")
  1
  2
  ==> 2
  Io> for(i, 1, 2, i println, "extra argument")
  2
  ==> extra argument

In the first form, “extra argument” is really extra. In the second form, you’ve omitted the optional increment argument, and that effectively shifted everything to the left. Your “extra argument” is now the message, and you’re working in steps of i println, which returns i. If that line of code is buried deeply into a complex package, Io just puked in your car. Sometimes, you have to take the bad with the good. Io gives you freedom. Sometimes, freedom hurts.

The if control structure is implemented as a function with the form if(condition, true code, false code). The function will execute true code if condition is true; otherwise, it will execute false code:

  Io> if(true, "It is true.", "It is false.")
  ==> It is true.
  Io> if(false) then("It is true") else("It is false")
  ==> nil
  Io> if(false) then("It is true." println) else("It is false." println)
  It is false.
  ==> nil

You’ve spent some time on control structures. Now, we can use them to develop our own operators.

Operators

Like with object-oriented languages, many prototype languages allow for syntactic sugar to allow operators. These are special methods like + and / that take a special form. In Io, you can see the operator table directly, like this:

  Io> OperatorTable
  ==> OperatorTable_0x100296098:
  Operators
  0 ? @ @@
  1 **
  2 % * /
  3 + -
  4 << >>
  5 < <= > >=
  6 != ==
  7 &
  8 ^
  9 |
  10 && and
  11 or ||
  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.

You can see that an assignment is a different kind of operator. The number to the left shows the level of precedence. Arguments closer to 0 bind first. You can see that + evaluates before ==, and * before +, just as you would expect. You can override the precedence with (). Let’s define an exclusive or operator. Our xor returns true if exactly one of the arguments is true, and false otherwise. First, we add the operator to the table:

  Io> OperatorTable addOperator("xor", 11)
  ==> OperatorTable_0x100296098:
  Operators
  ...
  10 && and
  11 or xor ||
  12 ..
  ...

You can see the new operator in the right place. Next, we need to implement the xor method on true and false:

  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)
  )

We’re using brute force here to keep the concepts simple. Our operator behaves exactly like you would expect:

  Io> true xor true
  ==> false
  Io> true xor false
  ==> true
  Io> false xor true
  ==> true
  Io> false xor false
  ==> false

When all is said and done, true xor true gets parsed as true xor(true). The method in the operator table determines the order of precedence and the simplified syntax.

Assignment operators are in a different table, and they work a little bit differently. Assignment operators work as messages. You’ll see an example of them in action in Domain-Specific Languages. For now, that’s all we’ll say about operators. Let’s move on to messages, where you will learn to implement your own control structures.

Messages

As I was working through this chapter, one of the Io committers was helping me through a moment of frustration. He said, “Bruce, there’s something you have to understand about Io. Almost everything is a message.” If you look at Io code, everything but comment markers and the comma (,) between arguments are messages. Everything. Learning Io well means learning to manipulate them beyond just basic invocation. One of the most crucial capabilities of the language is message reflection. You can query any characteristic of any message and act appropriately.

A message has three components: the sender, the target, and the arguments. In Io, the sender sends a message to a target. The target executes the message.

The call method gives you access to the meta information about any message. Let’s create a couple of objects: the postOffice that gets messages and the mailer that delivers them:

  Io> postOffice := Object clone
  ==> Object_0x100444b38:
 
  Io> postOffice packageSender := method(call sender)
  ==> method(
  call sender
  )

Next, I’ll create the mailer to deliver a message:

  Io> mailer := Object clone
  ==> Object_0x1005bfda0:
 
  Io> mailer deliver := method(postOffice packageSender)
  ==> method(
  postOffice packageSender
  )

There’s one slot, the deliver slot, that sends a packageSender message to postOffice. Now, I can have the mailer deliver a message:

  Io> mailer deliver
  ==> Object_0x1005bfda0:
  deliver = method(...)

So, the object with the deliver method (mailer) is the object that sent the message. We can also get the target, like this:

  Io> postOffice messageTarget := method(call target)
  ==> method(
  call target
  )
  Io> postOffice messageTarget
  ==> Object_0x1004ce658:
  messageTarget = method(...)
  packageSender = method(...)

Simple enough. The target is the post office, as you can see from the slot names. Get the original message name and arguments, like this:

  Io> postOffice messageArgs := method(call message arguments)
  ==> method(
  call message arguments
  )
  Io> postOffice messageName := method(call message name)
  ==> method(
  call message name
  )
  Io> postOffice messageArgs("one", 2, :three)
  ==> list("one", 2, : three)
  Io> postOffice messageName
  ==> messageName

So, Io has a number of methods available to let you do message reflection. The next question is, “When does Io compute a message?”

Most languages pass arguments as values on stacks. For example, Java computes each value of a parameter first and then places those values on the stack. Io doesn’t. It passes the message itself and the context. Then, the receivers evaluate the message. You can actually implement control structures with messages. Recall the Io if. The form is if(booleanExpression, trueBlock, falseBlock). Let’s say you wanted to implement an unless. Here’s how you’d do it:

io/unless.io
  unless := method(
  (call sender doMessage(call message argAt(0))) ifFalse(
  call sender doMessage(call message argAt(1))) ifTrue(
  call sender doMessage(call message argAt(2)))
  )
 
  unless(1 == 2, write("One is not two\n"), write("one is two\n"))

This little example is beautiful, so read it carefully. Think of doMessage as somewhat like Ruby’s eval but at a lower level. Where Ruby’s eval evaluates a string as code, doMessage executes an arbitrary message. Io is interpreting the message parameters but delaying binding and execution. In a typical object-oriented language, the interpreter or compiler would compute all the arguments, including both code blocks, and place their return values on the stack. In Io, that’s not what happens at all.

Say the object westley sends the message princessButtercup unless(trueLove, ("It is false" println), ("It is true" println)). The result is this flow:

  1. The object westley sends the previous message.

  2. Io takes the interpreted message and the context (the call sender, target, and message) and puts it on the stack.

  3. Now, princessButtercup evaluates the message. There is no unless slot, so Io walks up the prototype chain until it finds unless.

  4. Io begins executing the unless message. First, Io executes call sender doMessage(call message argAt(0)). That code simplifies to westley trueLove. If you’ve ever seen the movie The Princess Bride, you know that westley has a slot called trueLove, and the value is true.

  5. The message is not false, so we’ll execute the third code block, which simplifies to westley ("It is true" println).

We’re taking advantage of the fact that Io does not execute the arguments to compute a return value to implement the unless control structure. That concept is extremely powerful. So far, you’ve seen one side of reflection: behavior with message reflection. The other side of the equation is state. We’ll look at state with an object’s slots.

Reflection

Io gives you a simple set of methods to understand what’s going on in the slots. Here are a few of them in action. This code creates a couple of objects and then works its way up the prototype chain with a method called ancestors:

io/animals.io
  Object ancestors := method(
  prototype := self proto
  if(prototype != Object,
  writeln("Slots of ", prototype type, "\n---------------")
  prototype slotNames foreach(slotName, writeln(slotName))
  writeln
  prototype ancestors))
 
 
  Animal := Object clone
  Animal speak := method(
  "ambiguous animal noise" println)
 
  Duck := Animal clone
  Duck speak := method(
  "quack" println)
 
  Duck walk := method(
  "waddle" println)
 
  disco := Duck clone
  disco ancestors

The code is not too complicated. First, we create an Animal prototype and use that to create a Duck instance, with a speak method. disco’s prototype is Duck. The ancestors method prints the slots of an object’s prototype and then calls ancestors on the prototype. Keep in mind that an object can have more than one prototype, but we don’t handle this case. To save paper, we halt the recursion before printing all of the slots in the Object prototype. Run it with io animals.io:

Here’s the output:

  Slots of Duck
  ---------------
  speak
  walk
  type
 
  Slots of Animal
  ---------------
  speak
  type

No surprises there. Every object has a prototype, and those prototypes are objects that have slots. In Io, dealing with reflection has two parts. In the post office example, you saw message reflection. Object reflection means dealing with objects and the slots on those objects. No classes are involved, anywhere.

What We Learned in Day 2

If you’re still following, day 2 should have been a breakthrough day of sorts. You should know enough Io to do basic tasks with a little support from the documentation. You know how to make decisions, define methods, use data structures, and use the basic control structures. In these exercises, we’ll put Io through its paces. Get thoroughly familiar with Io. You will really want to have the basics down when we move into problems that stretch Io into the metaprogramming and concurrency spaces.

Day 2 Self-Study

Do:

  1. A Fibonacci sequence starts with two 1s. Each subsequent number is the sum of the two numbers that came before: 1, 1, 2, 3, 5, 8, 13, 21, and so on. Write a program to find the nth Fibonacci number. fib(1) is 1, and fib(4) is 3. As a bonus, solve the problem with recursion and with loops.

  2. How would you change / to return 0 if the denominator is zero?

  3. Write a program to add up all of the numbers in a two-dimensional array.

  4. Add a slot called myAverage to a list that computes the average of all the numbers in a list. What happens if there are no numbers in a list? (Bonus: Raise an Io exception if any item in the list is not a number.)

  5. Write a prototype for a two-dimensional list. The dim(x, y) method should allocate a list of y lists that are x elements long. set(x, y, value) should set a value, and get(x, y) should return that value.

  6. Bonus: Write a transpose method so that (new_matrix get(y, x)) == matrix get(x, y) on the original list.

  7. Write the matrix to a file, and read a matrix from a file.

  8. Write a program that gives you ten tries to guess a random number from 1--100. If you would like, give a hint of “hotter” or “colder” after the first guess.