Seven Languages in Seven Weeks – Io – Day 2

Wednesday, January 26, 2011

“Day 2″ with Io on my more and more misnamed quest. In this chapter Bruce explains basics of conditionals, looping, operator definition and reflection. He also spends a bit more time to explain message passing in Io.

In conditionals and looping section we get overview of some typical looping and branching structures. Such as a simple infinite loop that will keep evaluating until a break:

loop("you spin me right round..." println)

This can come in handy when implementing things such as servers etc. We also get to meet our good old friends (often seen in most languages) for and while loop:

i := 1
while(i <= 10, i println; i = i + 1)

for(i, 1, 10, i println)

for(i, 10, 1, -1, i println)

First two lines are an example of a while loop. It will print numbers from 1 to 10. While loop takes a condition as a first parameter and it will keep evaluating statements passed in second parameter while the condition holds. Lines 4 and 6 are examples of a for loop. First one prints numbers from 1 to 10 and second one counts back from 10 to 1. This is due to the fact that second for loop utilises the optional step parameter. The parameters of a for loop method are: the name of the counter, the first value, the last value, optional step, and a message with sender. As we can see from example step parameter can be negative.

Next we learn about if method whose basic form is: if(condition, true code, false code) and that it if comes in following two flavours:

if(true, "It's true" println, "It isn't true" println)

if(true) then(
    "It's true" println
) else(
    "It isn't true" println

The syntax of first option is if(condition, true code, false code). The second option is self explanatory, I hope.

You might have noticed that I refer to “for”, “while” and “if” as methods. Which is in fact exactly what they are, as there are barely any keywords in Io due to its minimal syntax. All of them are merely methods defined on Object object. :)

Next we move to operators which as you might have guessed by know are again just methods. All of the operators are defined in OperatorTable object which also holds operator precedence data. So to add a new operator all we have to do is add the operator to the table and define the method on the objects that we wish to support the operator on. Example based on logical implication:

// hint: type OperatorTable in Io console for list of all
// supported operators and their precedence

OperatorTable addOperator("=>", 12)

true => := method(bool, bool)
false => := true

We then proceed to learn a bit about message passing and reflection. Every message has three components: sender that sends the message, target that executes the message and arguments. We learn about the fact, that we can access all the information about the current message via “call” method exposed in each block (method). This method provides access to Call object which stores information and methods related to currently executed method . For instance this is the way to access message name and arguments:

mirror := Object clone

mirror reflect := method(
    call message name println
    call message arguments println

Io> mirror reflect(1, "two", 3)
list(1, "two", 3)
==> list(1, "two", 3)

Armed with this knowledge we are asked to tackle 8 “homework” tasks:

HW1: 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.

fib := method(n, if (n < 3, 1, fib(n - 2) + fib (n - 1)))

fibIter := method(n,
    previous := 1
    current := 1
    for (i, 3, n,
        buffer := current
        current = previous + current
        previous = buffer
    return current

for (i, 1, 10, "rec:#{fib(i)} iter:#{fibIter(i)}" interpolate println)

I am used to programming in iterative style but I must say that this task lent itself naturally to recursion, which lead to a very simple one liner. Both methods rely on the fact that there is no need to calculate first 2 positions and 1 can be returned in that case. Armed with first 2 positions calculating the rest of the sequence is trivial.
In recursive variant you can also see that I rely on the fact that return method (yep method again) is optional. If you don’t provide it method will simply return the value of the last evaluated statement.

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

Number originalDivision := Number getSlot("/")

Number / := method(denominator,
    if (denominator == 0, return 0, self originalDivision(denominator))

(1 / 2) println
(1 / 0) println
(2 / 1) println

I found this task quite interesting as I was wondering how to change the existing method without overwriting it. The solution I came up with is to just simply store original method in a new slot and redefine the division slot. The newly defined division slot then treats the exception when denominator is 0 and passes “business as usual” calculation to the original method.

HW3: Write a program to add up all of the numbers in a two-dimensional

sum2d := method(array, array flatten sum)

sum2d(list(list(1, 2, 3), list(1, 2, 3), list(1, 2, 3))) println

This one almost falls under the easier done than said section. First list is flattened into single dimension and then sum of flattened list’s elements is returned. Easy peasy.

HW4: 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.)

List myAverage := method((self sum) / (self size))

List myAverage1 := method(
    self map(element, if (element type == "Number", element, 0)) myAverage

List myAverage2 := method(
    sum := 0
    counter := 0
    self foreach(element,
        if (element type != "Number", Exception raise("Not a number element"))
        sum = sum + element
        counter = counter + 1
    sum / counter

a := list(1, 2, 3, 4, 5, 6, 7)

a myAverage println
list("a", "b", "c") myAverage1 println
a append("a")
a myAverage1 println
list(1,2,3) myAverage2 println
a myAverage2

I have solved this task in three different ways.
First relies on methods provided by List prototype. It is very simple and reads well but it doesn’t handle non number elements.
Second option first maps the List into a new List where non Number elements are replaced by 0 and then invokes “myAverage” method on it.
Third option is the most verbose but I wanted to solve the problem with counters as well.

HW5: 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.
HW6: Bonus: Write a transpose method so that (new_matrix get(y, x)) == matrix get(x, y) on the original list.
HW7: Write the matrix to a file, and read a matrix from a file.

List2d := List clone do(
    dim := method(x, y, self setSize(y) mapInPlace(list() setSize(x)))

    set := method(x, y, value, self at(y) atPut(x, value))

    get := method(x, y, self at(y) at(x))

    transpose := method(
        newX := self size
        newY := self first size
        newMatrix := List2d clone dim(newX, newY)
        for(x, 0, newX -1,
            for (y, 0, newY -1,
                newMatrix set(x, y, get(y, x))
        return newMatrix

setAndDisplay := method(matrix, x, y, value,
    matrix set(x, y, value)
    matrix get(x, y) println
    matrix println

matrix := List2d clone dim(3, 2)
matrix println

setAndDisplay(matrix, 0, 0, 5)
setAndDisplay(matrix, 1, 0, 10)
setAndDisplay(matrix, 2, 0, 15)

setAndDisplay(matrix, 0, 1, 20)
setAndDisplay(matrix, 1, 1, 25)
setAndDisplay(matrix, 2, 1, 300)

setAndDisplay(matrix, 2, 1, 30)

matrix transpose println

File with("matrix.dat") open write(matrix serialized) close
matrix = nil
matrix println
matrix := doRelativeFile("matrix.dat")
matrix println

“dim” method does the job by setting the size of the main list to y, as task prescribes. Next “mapInPlace” method is used to update each element of that field by setting it to a list with size of x. Beautiful. I will not explain “set” and “get” methods as I think they are easy enough to understand.
“transpose” method works on the principle of inspecting a List2d that we’re transposing and creating a new instance with swapped dimensions. Iterating through the elements and swapping their coordinates does the rest of the job.
In regards to saving a matrix to a file and reading from it, I resorted to simple serialization. As Io serializes its objects into Io scripts, all I have to do to deserialize matrix from the file is to execute the contents of the file by virtue of “doRelativeFile” method.

HW8: 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.

number := Random value(1, 100) round
previousDif := nil

    guess := File standardInput readLine("Take a guess: ") asNumber

    if(guess isNan, continue)
    if(guess == number, break)

    dif := (number - guess) abs

        if(previousDif > dif) then(
            "Warmer" println
        ) elseif(previousDif < dif) then(
            "Colder" println
        ) else(continue))

    previousDif = dif

"You da man" println

I don’t think there is much need to spend too many words on this task. It is quite self explanatory. The only new concepts introduced are generating a random number and reading from standard input, but they are very straightforward as you can see.

If I try to sum up my impressions after day 2 I must say that I am still finding it hard to wipe the smirk of my face when playing with Io. It is just so full of pleasant surprises and combined with the simplicity of the language it is hard for any self confessed geek not to like it IMHO.

Happy cloning!!

One Comment

    Leave a Reply

    Pingbacks & Trackbacks

    1. Dalibor Novak » Seven Languages in Seven Weeks – Io – Day 3 - Pingback on 2011/03/06