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.


6:06 AM
"Tomorrow" is taking a long time Scott :-) But great post, please keep 'm coming