Question

I'm not quite sure how variable scope works in Io. The docs say it has closures, but I don't seem to be able to see idx from within the next and prev methods. Parent visibility is the key premise of closures, so how can they work?

List iterator := method(
    idx := 0

    itr := Object clone
    itr next := method(
        idx = idx + 1
        return self at(idx)
    )

    itr prev := method(
        idx = idx - 1
        return self at(idx)
    ) 

    return itr
)

How should this be achieved?

Was it helpful?

Solution

So you're fundamentally misunderstanding how methods and blocks work, but that's ok. Let's go over the basics:

  1. Methods are blocks who activate when you call them by name, and whose scope is set to nil. When we talk about scope.
  2. Blocks have their scope set to the context in which they're created.

context means a locals object, basically a stack frame. Scope means who will be the "sender" of the block activation when the block/method is invoked. You can access this by the call sender object inside the context of a method or block.

Now, let's look at your code. It's almost perfect, there's only one thing missing, and it's non-obvious.

Since methods have dynamic scope, their scope message returns nil. This signifies to the evaluator that whichever object received that message, should be passed in as the sending context. We don't want that behaviour, we want to capture some scope, specifically the locals of the iter method we've defined. Let's look at a corrected example:

List iterator := method(
    idx := 0

    itr := Object clone
    itr next := method(
        idx = idx + 1
        at(idx)
    ) setScope(thisContext)

    itr prev := method(
        idx = idx - 1
        at(idx)
    ) setScope(thisContext)

    itr
)

I've simplified the bodies, but they haven't changed in terms of functionality (apart from a few less message sends). The important thing is the setScope call passed to the method before assignment to next/prev. I could have chose to rewrite this as:

iter prev := block(
    idx = idx - 1
    at(idx)
) setIsActivatable(true)

but I'd have then had to make the block activatable, since blocks are not activatable by default. The code above, and the corrected itr prev using method() are functionally equivalent.

Methods are not closures, blocks are. A block is simply a method whose scope is non-nil, they are the same object.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top