10.02.2006
5:57 AM

Caching values in messages is really neat, I have to admit, but it is oftentimes rather verbose. To illustrate this point, I'll give you an example of a curry function without any helper functions.

curry := method(proc, left,
   IoVM getSlot("Block") clone setArgumentNames(list("right")) setMessage(
       Message clone setCachedResult(getSlot("proc")) setAttached(
           message(call) clone setArguments(list(
               Message clone setCachedResult(getSlot("left")),
               message(right)
           ))
       )
   ) setIsActivatable(true)
)

add1 := curry(method(x, y, x + y), 1)
add1(2) println // 3

Now, this is not exactly what I would call elegant! Not yet, anyway. One of the major things here is that we have to explicitly clone the Block, and set all of its parameters up manually. method() is a lot nicer for these sorts of things. Unfortunately, that doesn't leave us with a nice way to cache values within the message. So, we define a helper function, Block prependMessage(), and redefine our curry to use that.

getSlot("Block") prependMessage := method(msg,
   setMessage(msg setNext(getSlot("self") message))
   getSlot("self")
)

curry := method(proc, left,
   method(right,
       getSlot("proc") call(getSlot("left"), getSlot("right"))
   ) prependMessage(
       message(setSlot) clone setArguments(list(
           Message clone setCachedResult("proc"),
           Message clone setCachedResult(getSlot("proc"))
       )) setNext(
       message(setSlot) clone setArguments(list(
           Message clone setCachedResult("left"),
           Message clone setCachedResults(getSlot("right"))
       )))
   )
)

add2 := curry(method(x, y, x + y), 2)
add2(1) // 3

Uh-oh. That's really not that much better at all! Maybe we abstract out that setSlot() stuff? Here we'll define an attach() method that'll do that repetitive work for us.

getSlot("Block") prependMessage := method(msg,
   setMessage(msg setNext(getSlot("self") message))
   getSlot("self")
)

getSlot("Block") attach := method(name,
   prependMessage(message(setSlot) clone setArguments(list(
       Message clone setCachedResult(name),
       Message clone setCachedResult(call sender getSlot(name))
   )))
   getSlot("self")
)

curry := method(proc, left,
   method(right,
       getSlot("proc") call(getSlot("left"), getSlot("right"))
   ) attach("proc") attach("left")
)

add2 := curry(method(x, y, x + y), 2)
add2(1) println // 3

Wow, that really is a lot better! What we've got here is an explicit closure. We define a method, then we tell it what names to import from the enclosing scope. Now sure, that's all well and good, if you want to name things. But I went a step further and created something kind of monstrous. It's called Message interpolate(). It's a lot like Sequence interpolate(), in that it takes values from the outlying scope and puts them into the message. I abstrated out some of it, creating Message map(), which may just have other uses.

Message map := method(
   name := call argAt(0) name
   body := call argAt(1)

   call sender setSlot(name, self)
   call sender doMessage(body)
  
   branch := self
   while(branch,
       branch arguments foreach(arg,
           call delegateTo(arg)
       )
       branch := branch attached
   )
  
   if(next,
       call delegateTo(next)
   )
  
   self
)

Message interpolate := method(sender,
   if(call argCount ==(0),
       sender := call sender
   )
 
   self map(branch,
       if(branch name == "curlyBrackets",
           branch setCachedResult(sender doMessage(branch arguments at(0)))
       )
   )
)

IoVM getSlot("Block") interpolate := method(
   (result := getSlot("self") clone) message interpolate(call sender)
   getSlot("result")
)

Object curry := method(
   method(right,
       {call evalArgAt(0)} call({call evalArgAt(1)}, getSlot("right"))
   ) interpolate
)

add1 := curry(method(x, y, x + y), 1)
add1(2) println // 3

Now that's a sexy curry.


9.20.2006
5:59 AM

I haven't posted anything in a while! Oh wow. Too long.

So here's something to make up for it. It's a lazy macro: is a function that, once run, modifies the code that calls it. You get a slowdown the first time the code is run, but afterwards, it is as if you wrote the generated code straight into the function. It's an amazingly powerful technique that, coupled with message caching, can do things that you just can't do in most other languages.

Here's a macro that gives us a shortcut: 'foo will become getSlot("foo"). It's really quite handy, as getSlot is a common operation. The ' symbol was chosen because of its use in Lisp: that is, to prevent Lisp from evaluating the symbol. That's similar to what Io does here: by using getSlot, you are retrieving objects that would otherwise activate without activating them.

Object ' := method(
    name := call argAt(0) name
    attached := call argAt(0) attached
    call message setName("getSlot") setArguments(list(name box))
    if(call message attached,
        call message setAttached(call message attached setAttached(attached))
    ,
        call message setAttached(attached)
    )
    call sender getSlot(name)
)

' gives us a small problem, initially. It's actually an operator, and parsing it captures quite a bit more than expected. We only want the first symbol after the quote, so we have to modify the scheme a bit. We take the first argument to ' and move all of its attached methods to the actual message's attached messages.

With this adjustment, we get 'foo bar to look like '(foo) bar instead of '(foo bar).

Of course, that's not the whole story. We rename the message to "getSlot", so that we're calling the function that we want. That's easy enough: just do a call message setName("getSlot"). However, what we have now as an argument to getSlot is just a message. We need to give it a string. And we'll need to store it in a message. How do we do that?

Here I'm going to define a utility function called box. This creates a new message and stores the receiving object inside it, as the cached value, so that when the message is evaluated, the result is our original object.

Object box := method(
    Message clone        setName(getSlot("self") asSimpleString)        setCachedResult(getSlot("self"))
)

Now we just have to box our string up and set it to the arguments of our original message. With the macro complete, it only takes one pass over a ' to change it into a getSlot call.

foo := method(bar,
    'bar println
)

getSlot("foo") println

foo("Hello, Io!")

getSlot("foo") println

foo("Goodbye, Io!")

/*
method(bar, '(bar println))
Hello, Io!
method(bar, getSlot("bar") println)
Goodbye, Io!
*/


8.31.2006
5:22 AM

In a belated response to netytan, I post my own any? and all? written in Scheme.

(define (any? proc ls)
    (if (null? ls)
        #f
        (if (proc (car ls))
            #t
            (any? proc (cdr ls)))))

;; (any? even? '(1 3 5 8 7)) => #t, shortcircuiting at 8
;; (any? odd?  '(2 4 6 8 10)) => #f

(define (all? proc ls)
    (if (null? ls)
        #t
        (if (proc (car ls))
            (all? proc (cdr ls))
            #f)))

;; (all? even? '(2 4 6 1 2 3)) => #f, shortcircuiting at 1
;; (all? odd?  '(1 3 5 7 9 11)) => #t

Just because.


8.25.2006
9:03 AM

I've just spent a bit of time working with Syntax, a Ruby module made for doing syntax highlighting. I figured that since Io syntax was so simple, it would take little effort to make it work for me. Well, hey, I was right. Here's a little example of the results:

List docSlot("splat(*names)", "Assign one value from the list to each given symbol, starting from index 0.")
List splat := method(
    names := call message arguments map(arg, arg name)
    if(names size < size,
        Exception raise("Too many values to unpack")
    )
    if (names size > size,
        Exception raise("Need more than #io{size} values to unpack" interpolate)
    )
    0 to(size - 1) foreach(i,
        call sender setSlot(names at(i), at(i))
    )
    self
)

List => := List getSlot("splat")

fibo := method(n,
    a := 0
    b := 1
    
    0 to(n - 1) foreach(i,
        list(b, a + b) => (a, b)
    )
    a
)

fibo(10) println // 55

And here's the implementation:

require 'syntax'
require 'syntax/convertors/html'

class IoTokenizer < Syntax::Tokenizer
    IO_IMPORTANT = %w{
        and
        Block block
        call clone
        do
        getSlot
        List list
        Map message method
        newSlot not Number
        Object or
        print println
        self Sequence setSlot
        to
        with write writeln
    }
    
    def scan_string
        start_region :string
        loop do
            chunk = scan_until(/"/)
            if chunk[-2] and chunk[-2].chr == "\\"
                append chunk
            else
                append chunk[0..-2]
                break
            end
        end
        end_region :string
    end
    
    def step
        if comment = scan(/(#|\/\/).*#{EOL}/)
            start_group :comment, comment
        elsif comment = scan(/(\/\*).*?(\*\/)/)
            start_group :comment, comment
        elsif scan(/"""/)
            start_group :punctuation, '"""'
            start_group :string, scan_until(/"""/)[0..-4]
            start_group :punctuation, '"""'
        elsif scan(/"/)
            start_group :punctuation, "\""
            scan_string
            start_group :punctuation, "\""
        elsif digits = scan(/\d+(\.\d+)?(e\d+)?/)
            start_group :digits, digits
        elsif punc = scan(/[\(\)\[\]\{\}]|=|:=|;/)
            start_group :punctuation, punc
        elsif word = scan(/[A-Z][a-zA-Z_:\.]+/)
            if IO_IMPORTANT.include? word
                start_group :"important upcase", word
            else
                start_group :upcase, word
            end
        elsif word = scan(/[a-zA-Z_:\.]+/)
            if IO_IMPORTANT.include? word
                start_group :important, word
            else
                start_group :normal, word
            end
        elsif operator = scan(/[!@$%^&*+|\-]+/)
            start_group :operator, operator
        elsif
            start_group :normal, getch
        end
    end
end

Syntax::SYNTAX["io"] = IoTokenizer


8.22.2006
4:25 AM

Mixins define a set of methods that can be "mixed in" to a class definition in order to provide additional functionality. Of course, this often leads to another construct, such as the Ruby Module construct. But Io isn't Ruby.

We don't need a seperate construct for classes, and we certainly don't need a seperate construct for mixins. You can define your mixins just like any other objects, like this one, a clone of the Comparable module from Ruby:

Comparable := Object clone do(
    == := method(other,
        self compare(other) == 0
    )
    < := method(other,
        self compare(other) == -1
    )
    > := method(other,
        self compare(other) == 1
    )
    <= := method(other,
        self compare(other) != 1
    )
    >= := method(other,
        self compare(other) != -1
    )
    isBetween := method(this, that,
        (self > this) and (self < that)
    )
    removeAllProtos  # Remove Object from the mixin. It's going to cause trouble.
)

This mixin provides a number of comparison operators when the target object provides the "compare" method. Now, we just have to make sure that our mixin is on the top of our prototypes list, and therefore first in line for slot lookups. This example creates an object called SizeMatters, which inherits from Sequence, and then prepends the Comparable mixin to its prototypes:

SizeMatters := Sequence clone do(
    prependProto(Comparable)     # Here we prepend Comparable, putting it
                                 # first in line for lookups.
    compare := method(other,
        size compare(other size)
    )
    
    with := method(str,
        str clone setProto(SizeMatters)
    )
)

We can see this working here:

s1 := SizeMatters with("z")
s2 := SizeMatters with("yy")
s3 := SizeMatters with("xxx")
s4 := SizeMatters with("wwww")
s5 := SizeMatters with("vvvvv")

s1 < s2              // true
s4 == s5             // false
s4 isBetween(s1, s3) // false
s4 isBetween(s3, s5) // true

Io uses a form of multiple inheritence to accomplish this. Your new SizeMatters object inherits from Comparable first, and Sequence second. But since Sequence will provide most of the functionality for SizeMatters, we want to list it first, and so we do. Very dynamic.

Oh, and it's not only easier to understand. It also means that you can insert inheritence wherever you want, like in an already-existing object:

Number protos == list(Object)             // true
Number do(
    == := getSlot("==")                   // Overridding Number == is bad.
) prependProto(Comparable)                // Shove Comparable into the dependencies.
Number protos == list(Comparable, Object) // true

4 isBetween(2, 5) println                 // true

Now, isn't that nice? Ruby modules without Modules.


8.06.2006
5:23 AM

I've given a lot of thought to the use of lexical scopes and shadowing slots in Io, and I've come up with a bit of a monster that throws itself against the problem until it breaks every vertebrae. His name is LexicalScopeOperator.

LexicalScopeContainer := Object clone do(
    clone := getSlot("clone")
    init := method(.setSlot("call", call sender call))
    forward := method(call delegateTo(self call sender))
    doMessage := getSlot("doMessage")
    methodList := list("setSlotWithType", "updateSlot", "getSlot", "hasLocalSlot",
        "hasSlot", "ancestorWithSlot", "removeSlot", "setSlot")
    genericMethod := method(name,
        Protos getSlot("Block") clone setArgumentNames(list("name")) setMessage(
            "if(name in(self call shadowed),
                call delegateToMethod(self, \".#io{name}\")
            ,
                call delegateTo(self call sender)
            )" interpolate asMessage
        ) setIsActivatable(true)
    )
    methodList foreach(mthd,
        setSlot("." .. mthd, getSlot(mthd))
    )
    methodList foreach(mthd,
        .setSlot(mthd, genericMethod(mthd))
    )
    list("genericMethod", "methodList") foreach(var, .removeSlot(var))
    removeAllProtos
)

The original code was much longer; I opted for some meta-programming in order to get rid of some nasty repetition. genericMethod generates the needed functions, and hides them each in ".functionName". So, "getSlot" is stored in ".getSlot", and "getSlot" is replaced by a new method. These new methods check to see if the named slot is "shadowed" by the lexical scope, and if not, delegates again out to "self call sender". Everything else is delegated out. Of course, you probably have no idea what you could possibly use this thing for, or what it does, for that matter. So I introduce to you let.

Now, let comes from Lisp. It creates a new lexical scope with a given list of symbols and values, and lets those symbols represent those values within that scope only. On top of that, any closures created within the let would grab the value from inside of the let and keep it afterwards.

(setf 'x 1)
(print x)     ;; 1
(let ((x 2))
    (print x) ;; 2
)
(print x)     ;; 1

It doesn't do this by doing any ugly temporary slot sets like the functions I posted a few days ago. Lisp's variable lookup is quite different from Io's, afterall. So I hacked at it, and came up with the monster from above. The let I came up with that uses LexicalScopeContainer is defined like this:

let := method(
    shadowing := call message arguments slice(0, -1)
    body := call message arguments last
        
    container := LexicalScopeContainer clone
    
    call shadowed := list

    shadowing foreach(msg,
        if(msg name == "setSlot",
            call shadowed append(call sender doMessage(msg arguments first))
            container doMessage(msg)
        ,
            call shadowed append(msg name)
        )
    )
    
    container doMessage(body)
)

And it is used like this:

x := 1
x println     // 1
let(x := 2,
    x println // 2
)
x println     // 1

All blocks defined also work properly:

let(x := 2,
    f := block(x + 1)
)
f println     // 3

I also decided to redefine expand in these terms:

List expand := method(
    call shadowed := call message arguments slice(0, -1) map(name)
    body := call message arguments last
    
    container := LexicalScopeContainer clone
    
    call shadowed foreach(i, name,
        container .setSlot(name, at(i))
    )
    
    container doMessage(body)
)

list(1, 2, 3) expand(x, y, z,
    x println // 1
    y println // 2
    z println // 3
    return x + y + z
)     println // 6

x     println // 0

Of course, I am working on a way to refine the beast, as it is rather voodoo-magical. But it certainly works better than the other two solutions I came up with on the way. I'll share those tomorrow.


8.03.2006
7:40 AM

Over at Pinupgeek's blog you'll find something very interesting: a Python->Io bridge! I can't test it myself as it isn't Windows-compatible (nor is Io at the moment for that matter), but please, do take a look.


6:05 AM

The list method expand is a flow control construct designed to make it easier to deal with multiple return values stashed in a list without having to explicitly unpack it.

List expand := method(
    argNames := call message arguments slice(0, -1) map(name)
    body := call message arguments last
    temp := Object clone
    sender := call sender
    
    argNames foreach(i, name,
        temp setSlot(name, sender getSlot(name))
        sender setSlot(name, at(i))
    )
    
    result := call sender doMessage(body)
    
    argNames foreach(name,
        sender setSlot(name, temp getSlot(name))
    )
    
    result
)

x := 0

list(1, 2, 3) expand(x, y, z,
    x println // 1
    y println // 2
    z println // 3
    x + y + z
)     println // 6

x println     // 0

Notice that even though I didn't create a new block, the variables are restored upon returning. This should cover most cases. Edge cases created by trying to access sender slots with the same names as the passed arguments will fail.

The next method is seperate, which has two forms. The first form is the shorthand form, which takes one argument. This is a message to pass to each item on the list. If the message returns true, the item is put into the "passed" list. Otherwise, it is put into the "failed" list. The second form takes a symbol and message as its arguments, setting that slot to the current item and then performing the message.

List seperate := method(
    passed := list ; failed := list
    if(call message arguments size == 1,
        meth := call argAt(0)
        foreach(e,
            if(e doMessage(meth, call sender),
                passed append(e)
            ,
                failed append(e)
            )
        )
        list(passed, failed)
    ,
        argName := call argAt(0) name
        stored := call sender getSlot(argName)
        foreach(e,
            call sender setSlot(argName, e)
            if(call sender doMessage(call message arguments last),
                passed append(e)
            ,
                failed append(e)
            )
        )
        list(passed, failed)
    )
)

list(1, 2, 3, 4, 5) seperate(< 3) expand(lt, gt,
    lt println // list(1, 2)
    gt println // list(3, 4, 5)
)

list(1, 2, 3, 4, 5) seperate(x, x < 3) expand(lt, gt,
    lt println // list(1, 2)
    gt println // list(3, 4, 5)
)

This last one is a new implementation of sortBy, which also comes in two forms. The shortcut form is my much preferred one because of its brevity, but I figured I couldn't include just the one form if I was going to stay true to the rest of my methods (and Io's methods, really). The first form simply takes a message to do in the context of each item. The second form takes two argument names and a message to do in the context of the sender.

List sortBy := method(
    if(size == 0, list,
        if(call message arguments size == 1,       
            meth := call argAt(0)

            x := first doMessage(meth)            
            slice(1) seperate(doMessage(meth) < x) expand(lt, gt,
                call delegateTo(lt) append(first) appendSeq(call delegateTo(gt))
            )
        ,
            arg1 := call argAt(0) name
            arg2 := call argAt(1) name
            msg  := call argAt(2)
            
            temp1 := call sender getSlot(arg1)
            temp2 := call sender getSlot(arg2)
            
            call sender setSlot(arg2, first)
            
            slice(1) seperate(e,
                call sender setSlot(arg1, e)
                call sender doMessage(msg)
            ) expand(lt, gt,
                call sender setSlot(arg1, temp1)
                call sender setSlot(arg2, temp2)
                call delegateTo(lt) append(first) appendSeq(call delegateTo(gt))
            )
        )
    )
)

list("Hello", "Gel", "Foobar") sortBy(size) println                  // list("Gel", "Hello", "Foobar")
list("Hello", "Gel", "Foobar") sortBy(x, y, x size < y size) println // list("Gel", "Hello", "Foobar")

That about wraps it up for tonight. Tomorrow I should be doing some more work on the EasyParser library, so stay tuned. Also, the macros thing is a no-go, because Io has stopped compiling on Windows, and that particular feature isn't in the build I'm using. Ah well. Hopefully we'll be able to compile in Windows again once the new build setup is ready for us.