Seven Languages in Seven Weeks — (Io Lesson 2)

In stark contrast to the first day’s sparse lesson this one was quite extensive. Not only that but I spent a bit of time playing with some of the examples as well. That being the case, let’s start a little bit before the formal lesson with a re-tooling of duck.io

// Here it is bound to a free method.
inheritanceGraph := method(obj, lst,
  if(lst == nil , lst = List clone)
  methodList := lst at(0)
  fullName := lst at(1)
 
  if(methodList == nil, methodList = List clone)
  if(fullName == nil, fullName = "")
 
  prototype := obj proto
  thisName := ""
  fullName = "#{prototype type}.#{fullName}" interpolate
  if(prototype == Object,
     writeln("----------------------------")
     writeln("Inheritance graph for #{fullName}\n----------------------------" interpolate)
     methodList = methodList reverse
     usedTypes := List clone
     currentType:=""
     methodList foreach(fullSlotName,
       currentType = fullSlotName split(".::") at (1)
       if(usedTypes contains(currentType) == false, 
         writeln(fullSlotName)
         usedTypes append(currentType)
       , nil)
     )
     writeln("----------------------------")
 
     ,
     prototype slotNames foreach(slotName,
       methodList append("#{fullName}::#{slotName}" interpolate)
     )
     inheritanceGraph(prototype, list(methodList,fullName))  
  )
)
 
Animal := Object clone
Animal speak := method("...Making an animal sound..." println)
 
Duck := Animal clone
Duck speak := method("QUACK!" println)
Duck walk := method("waddle..." println)
 
disco := Duck clone
inheritanceGraph(disco)
 
/*OUTPUT
----------------------------
Inheritance graph for Object.Animal.Duck.
----------------------------
Animal.Duck.::speak
Animal.Duck.::type
Duck.::walk
----------------------------
*/

I like the output from this more condensed reflection format a bit better than the book’s long form. The book’s original example gave far too much information, spread over far too much screen real estate. In fact, I’m glad the book mentioned reflection as an explicit example as I was a bit annoyed by using slotNames all by itself to determine the Object’s messages. This isn’t perfect. If you clone from an object instance rather than a “class” definition, you’ll get some weird results. Of course, the convention is that you wouldn’t normally clone an “instance” object but rather a “class” anyway.

Overall, I’m glad I wrote that code above… but I feel it is probably a bit overwritten. Could probably be shorter, or perhaps more idiomatic (as much as anything can be idiomatic in a language with little syntax).

Okay, now on to the problems. For the most part I’m happy with the answers but I couldn’t understand what question #6 was even asking for so unfortunately I had to skip it.

/*
Write a program to find the nth Fibonacci number.
fib(1) is 1 -- (1)
fib(4) is 3 -- (1,1,2,3)
As a bonus, solve the problem with recursion and with loops.
*/
 
// Using Recursion...
fib := method(input, 
 
  // as a matter of personal taste I dislike
  // optional "extra" params in a weak-typed
  // enviornment like this, so I chose not
  // to allow fib to accept an optional list...
  // but we *need* that optional list here in
  // order to find the answer using recursion.
  // Hence, there's an inner recurse method
  // to handle the fib.
  recurse := method(input, lst,
    if(lst == nil , lst = List clone)
    if(input > 1, recurse (input - 1, lst))
    if(input == 1 or input == 2, lst append(1) return(lst))
    lst append(lst at(lst size - 2) + lst at (lst size - 1))
    return(lst)
  )
 
  finalList := recurse(input)
  return (finalList at (finalList size - 1))
)
 
writeln(fib(1)) // returns 1
writeln(fib(4)) // returns 3
writeln(fib(10)) // returns 55
 
 
 
// Using a loop 
fib = method(input,
  i := 0
  l := List clone append(1,1)
  while(i <= input,
    if(i > 2, l append(l at(l size - 2) + l at (l size - 1)))
    i = i + 1
  )
  //writeln(" fib seq is #{l} " interpolate)
  return(l at (input - 1))
)
 
writeln(fib(1)) // returns 1
writeln(fib(4)) // returns 3
writeln(fib(10)) // returns 55

The Horseman is about to admit something that might be surprising. This is in fact the first time he’s ever actually coded a Fibonacci algorithm. Reasoning through Fibonacci itself wouldn’t be terribly hard on its own but I struggled a bit to express the algorithm in this “foreign” language. Obviously it works, and I’m happy enough with the outcome.

/*
  How would you change / to return 0 if the
  denominator is zero?
*/
Number originalDivision := Number getSlot("/")
Number / := method(n,
  if(n == 0, return(0), return(call target originalDivision(n)))
)
 
x := 10 / 5 
writeln(x)  // Returns 2
x = 10 / 0
writeln(x)  // Returns 0

This does solve the problem of how to return 0 when dividing by 0. Was it the “correct” solution? I wasn’t sure how else you were supposed to access the original message associated with the “/” operator from within the override without first assigning it to a placeholder message. In that sense it feels correct, though it also feels like an act of polluting the Lobby.

/*
  Write a program to add up all the numbers in a 
  2-dimensional array
*/
sum2DArray := method(array2D,
  // One way...
  array2D foreach(i, val, array2D atPut(i, val sum))
  array2D sum
)
 
row0:=list(1,2,3,4)
row1:=list(1,2,3,5)
row2:=list(1,2,3,6)
table:=list(row0,row1,row2)
writeln(table)
tableTotal:=sum2DArray(table)
writeln(tableTotal) // Returns 33

This one was easy as pie. I grant you, I didn’t bother with any error checking here. As a side-note, only after solving the problem this way did I discover the “flatten” message, which would have made the foreach loop completely irrelevant.

/*
  Write a prototype for a 2-dimensional list.
  The dim(x,y) method should allocate a list of
  y lists that are x elments long.
  set(x,y,value) should set a value and get(x,y) 
  should return that value
*/
 
Array2D := Object clone
Array2D dim := method(x,y,
  //writeln("x#{x} y#{y} self#{self}" interpolate)
  self table := List clone
  self myX:=x
  self myY:=y
  for(a, 0, y - 1,
    l:=List clone
    for(b, 0, x - 1, l append(nil))
    table append(l)    
  )
  //writeln(table)
)
Array2D set := method(x,y,value,
  if(x >= myX or x < 0 or y >= myY or y < 0, writeln("Array out of bounds.  x should be less than #{myX} and y should be less than #{myY}" interpolate);return(nil))
 
  table at(y) atPut(x, value)
 
)
Array2D get := method(x,y,
  if(x >= myX or x < 0 or y >= myY or y < 0, writeln("Array out of bounds.  x should be less than #{myX} and y should be less than #{myY}" interpolate);return(nil))
  table at(y) at(x)
)
/* wasn't sure what the bonus question 6 was trying to ask... */
 
a2d := Array2D clone
a2d dim(2,3)
a2d set(3,4,"foo") // "Array out of bounds. x should be less than 2 and y should be less than 3" nil
a2d set(1,1,"foo") // "foo"
writeln(a2d get(1,1)) // "foo"
writeln(a2d get(0,0)) // nil
writeln(a2d get(6,6)) // "Array out of bounds. x should be less than 2 and y should be less than 3" nil

Again, easy as pie. The only thing that caused me a little bit of problem was remembering the scope rules for the table, but that was hardly a problem. As I said before though, I had no idea what question #6 was even trying to ask. I didn’t even know where to start answering it because I simply had no clue what the requirement was supposed to be.

This next question uses the same Array2D object from the previous example.

writeln(a2d serialized())
 
/* write contents of matrix to a file */
f := File with("foo.txt")
f openForUpdating
f write(a2d serialized())
f close
 
/* and read back from a file into a new object */
copyOfa2dFromFile := doFile("foo.txt")
writeln(copyOfa2dFromFile serialized())
writeln(copyOfa2dFromFile get(1,1))

This serialization task was hard. No, it doesn’t look difficult to code and it is not difficult to code. What’s difficult is finding the documentation on serialization and deserialization. I was about 3/4 of the way through a custom serialization method before I wondered whether this was already available as part of the language. Io’s official website wasn’t much help with this.

At last, The Horseman rode forth into the final challenge of day 2:

/** Guessing game 
  I was attempting to play with Io's scoping in
  this example.  I wanted to have the Game object
  create a game instance that has all the 
  requisite methods and variables *without*
  defining any of thsoe variables or methods
  on the Game "class" object itself.
**/
Game := Object clone
 
Game make := method(cheatMode,
  // change the value of g below
  // to manipulate exactly *what*
  // your game is.  Maybe you
  // really do want the Game
  // object to be altered?  
  // in that case you'd supply 
  // self.  Or maybe you want to
  // "gamify" the sender or the
  // target?  You could set those
  // instead of just an Object clone
 
  g := Object clone
 
  g remainingGuesses := 10
  g targetNumber := Random value(99 + 1) floor()
  g lastGuessOffBy := 0
 
  if(cheatMode == true,  writeln("getting a random number ... #{g targetNumber}" interpolate))
 
  g prompt := method(
    if(remainingGuesses < 1, writeln("You lose."); return())
    writeln("Guess a number between 1 and 100")
    writeln("You have #{remainingGuesses} guesses remaining" interpolate)
    //writeln("target number is #{targetNumber}" interpolate)
    userInput := File standardInput readLine("Your guess : ") asNumber
    check(userInput)
  )
  g check := method(input,
    writeln("input was #{input}" interpolate)
    //writeln("answer is #{targetNumber}" interpolate)
    if(targetNumber == input, 
 
      writeln(" You win! ") ; return (targetNumber) ,
 
      remainingGuesses = remainingGuesses - 1;
      dist := (input - targetNumber) abs();               
      if(dist < lastGuessOffBy, writeln("hotter"), writeln("colder"));
      lastGuessOffBy = dist;         
      prompt
    )
  )
  return(g)
)
 
gameStartPrompt:= method(
  selection := File standardInput readLine("Play a game.  Do you want to cheat? Y/n ")
  if(selection uppercase == "Y", 
     Game make(true) prompt ; return(1))
  if(selection lowercase == "n",
     Game make prompt ; return(1))
  gameStartPrompt
)
 
gameStartPrompt
 
// Test to make sure that we haven't 
// altered the slots of the Game object
writeln(Game getSlot("prompt"))

The game itself was trivial to write. The only thing that truly gave me a fit was trying to find information on how to accept user input. In the end, I found the answer to that question from the blog of Ben Nadel. I was in no way expecting it to be part of the File object prototype. Of course, the little section on the Io website about “File” did not mention anything at all about this use for the Object.

As you can see, the way I wrote the Game object was a bit… weird. It didn’t take long at all to create the Game object just as a normal definition the same way as all the other objects from previous sections, but I wanted to really play around more with scope. What you see above is similar to the example shown at the end of the Ruby chapter in which you can dynamically add behaviors to Classes via mixins. Since I define the game object’s scope at the beginning of the method, I can change only that one line of code if I want to make that function do something else, like for example return a new Game instance, or perhaps to pass it an object that I wish to “gamify” in which case Game becomes more of a tool to apply ad-hoc behaviors to given objects.

So, at more than halfway through the Io chapter The Horseman can say that he’s glad that he didn’t simply ride past. This language feels like what ECMA could have been in a saner world. It’s minimal and won’t stand in your way when you want to do real work, yet at the same time its syntax for creation, instantiation, and definitions of behaviors is clear and unambiguous. Of course, it’s so minimal that you may end up defining a great deal of your own convenience methods simply to work as quickly as you might in other languages.

At the same time, defining one’s own syntax and language constructs is a mighty siren’s song. Perhaps my tune will change after I’m through the end of the 3rd day, but my current feeling is that I’d like to know more about this language and where it’s being used. From a purely academic perspective, I find myself drawn to Io’s simplicity and power for much the same reason as I am drawn to C. The main difference is that you can learn Io in a fraction of the time, with what feels like a fraction of the headaches… and of course with the downside that you only understand a fraction of the underlying mechanic.

  1. No comments yet.