Today is Day 2 of Io in my Seven Languages in Seven Weeks series of blog posts. You can check out Day 1 of IO here.

Io, Day 2: Thoughts

Day 2 made some huge leaps and bounds over the basic syntax introduced in Day 1. The key learning from this day is that in Io, just about everything is a message sent to an object. There aren’t separate semantics for calling functions, using control structures, or defining objects: those are all just objects reacting to some sort of message. One of the most startling examples of this is the realization that even the basic operators in Io, such as +, -, and *, are actually messages. That is, the code 2 + 5 is actually understood as the message + being sent to the object 2 with 5 as a parameter. In other words, it could be re-written as 2 +(5). The +, then, is just a method defined on the number object that takes another number as a parameter. This makes supporting operators on custom objects simple: all I have to do is define a “slot” with the operator’s name. For example, here’s an object for complex numbers that can be used with the + operator:

Complex := Object clone do (
  setValues := method(real, imaginary, 
    self real := real; 
    self imaginary := imaginary; 
    self
  )
  + := method(other, 
    result := Complex clone setValues(self real + other real, self imaginary + other imaginary)
  ) 
)
 
c1 := Complex clone setValues(1, 2)
c2 := Complex clone setValues(3, 4)
 
c3 := c1 + c2
c3 println
 
/* 
 Complex_0x22ee90:
  imaginary        = 6
  real             = 4
*/

I found this fairly eye opening. As I think of the syntaxes of other languages I’m used to, such as Java, there are “special cases” all over the place. For example, the + operator has special code to handle addition for numbers and String concatenation and nothing else; for loops, while loops, if statements, defining classes, and so on are all special syntax features. In Io, they are all just objects responding to messages.

Io, Day 2: Problems

Fibonacci

Write a program to find the nth Fibonacci number. Both the recursive and iterative solutions are included:

fib_recursive := method(n,
  if(n < 3, 1, fib_recursive(n - 1) + fib_recursive(n - 2))
)
 
fib_iterative := method(n,
  prev_prev := 0
  prev := 0
  sum := 1
  for(i, 2, n, 
    prev_prev = prev
    prev = sum
    sum = prev + prev_prev
  )
  sum
)
 
"-- Recursive Fibonacci --" println
for(i, 1, 10, fib_recursive(i) println)
 
"" println
 
"-- Iterative Fibonacci --" println
for(i, 1, 10, fib_iterative(i) println)
 
/*

-- Recursive Fibonacci --
1
1
2
3
5
8
13
21
34
55

-- Iterative Fibonacci --
1
1
2
3
5
8
13
21
34
55

*/

Safe division

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

(1 / 0) println // inf
(1 / 2) println // 0.5
 
Number originalDivision := Number getSlot("/")
Number / := method(other, 
  if (other == 0, 0, self originalDivision(other))
)
 
(1 / 0) println // 0
(1 / 2) println // 0.5

2d add

Write a program to add up all the values in a 2-dimensional array.

sum2dArray := method(arr,
  arr flatten sum  
)
 
arr := list(list(1, 2), list(3, 4), 5, list(6, 7, 8), 9, 10)
sum2dArray(arr) println // 55

myAverage

Add a slot called myAverage to a list that computes the average of all the numbers in a list. Bonus: raise an exception if any item in the list is not a number.

List myAverage := method(
  sum := 0
  self foreach(i, v, if(v type == "Number", sum = sum + v, Exception raise("#{v} is not a Number" interpolate)))
  sum / (self size)
)

list(1) myAverage println                           // 1
list(3, 3, 3) myAverage println                     // 3
list(1, 2, 3, 4, 5, 6, 7, 8, 9) myAverage println   // 5
list(1, "A", 3) myAverage println                   // Exception: A is not a Number

Two Dimensional List

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. Write a transpose method so that new_matrix get(y, x) == original_matrix get(x, y). Write the matrix to a file and read the matrix from a file.

TwoDList := Object clone do (
  init := method(
    self lists := list()
  )
  
  dim := method(x, y,
    self lists preallocateToSize(y)
    for(i, 0, y - 1, self lists append(list() preallocateToSize(x)))
    self
  )
  
  set := method(x, y, value,
    self lists at(y) atInsert(x, value)
  )
  
  get := method(x, y,
    self lists at(y) at(x)
  )
  
  transpose := method(
    transposedList := TwoDList clone dim(self lists size, self lists at(0) size)
    self lists foreach(y, subList, 
      subList foreach(x, value,
        transposedList set(y, x, value)
      )
    )
    transposedList
  ) 
  
  println := method(
    self lists foreach(y, subList, 
      subList foreach(x, value, 
        if(x == 0 or x == subList size, "|" print)
        " #{get(x, y)} |" interpolate print        
      )
      "" println      
    )
  )
  
  toFile := method(name,
    File with(name) open write(self serialized) close
  )
  
  fromFile := method(name,
    doRelativeFile(name)
  ) 
)

list = TwoDList clone dim(2, 3)

list set(0, 0, "A")
list set(1, 0, "B")
list set(0, 1, "C")
list set(1, 1, "D")
list set(0, 2, "E")
list set(1, 2, "F")

transposedList := list transpose

transposedList toFile("transposed.txt")

transposedListFromFile := TwoDList fromFile("transposed.txt")

"2x3 list:" println
list println

"Transposed 3x2 list:" println
transposedList println

"Transposed 3x2 list from file:" println
transposedListFromFile println

/*

2x3 list:
| A | B |
| C | D |
| E | F |
Transposed 3x2 list:
| A | C | E |
| B | D | F |
Transposed 3x2 list from file:
| A | C | E |
| B | D | F |

*/

Guess Number

Write a program that gives you ten tries to guess a random number from 1-100. Give a hint of “hotter” or “colder” for each guess after the first one.

// Can't use Random due to http://stackoverflow.com/questions/3481651/how-do-i-import-an-addon-in-the-io-language
// toGuess := Random value(100) ceil

// A random number as per http://xkcd.com/221/
toGuess := 4

guessCounter := 0
previousDiff := nil
currentDiff := nil

while(guessCounter < 10, 
  guessCounter = guessCounter + 1  
  currentGuess := File standardInput readLine("Enter your guess: ") asNumber() 

  currentDiff := (toGuess - currentGuess) abs  
  if(currentDiff == 0) then (
    guessCounter = 10
  ) else (
    if (previousDiff == nil) then("Try again" println) elseif(currentDiff == previousDiff) then ("You just guessed that!" println) elseif(currentDiff < previousDiff) then ("Hotter" println) else("Colder" println)
    previousDiff = currentDiff
  )
)

if(currentDiff == 0, "Correct!", "Sorry, you ran out of guesses.") println

On to day 3!

Continue on to day 3 of Io here.