In between the first two parts of the Io chapter, I decided to take a quick side trip and explore a couple of little things. This file, contains code looking at various ways of iterating through and processing collections, in particular lists and maps. Familiar functions are to be found such as foreach, map and select/filter.
I also was able to discover the differences between blocks and methods in Io and find some possible improvements to the fakeClosure object I made on day 1.
So on to looking at some code. You might remember that on day 1 I wrote the following.
Note the comments saying that I explicitly need to do memo := fakeClosure memo if I want to call the method after its been assigned to the slot of another object (so that we could pretend its a completely self-contained function and quickly forget about the object). If we do not do this, Io will complain that memo is not defined.
This surprised me at first as I couldn't figure out why memo wouldn't resolve to fakeClosure memo by itself. The issue, which when you understand it, makes a lot of sense and turns out to be quite powerful, is that identifiers in Io methods are dynamically scoped. That is as the program is running it does the lookup memo -> is memo a local -> is memo in self, when we call fakeClosure fibmemo(x), the lookup is memo -> self memo -> fakeClosure memo; which returns the map we made earlier.
When we do fibmemo := fakeClosure getSlot("fibmemo"), the lookup for memo turns into memo -> self memo -> Object memo; which is not found. For the outer fibmemo method, self == Object, which doesn't have a memo slot (that is what we were trying to avoid in the first place, sticking this datastore in the global namespace). So how can we work around this? The answer is to use a block.
Blocks in Io are like methods except that they are lexically scoped. That is identifier lookup takes place in the context of where the block is defined in the code (as opposed to where it may be at runtime). Thus if we do
It will work just fine and we can refer to fakeClosure's memo slot no matter where this block gets attached. I also discovered the do() message that lets one initialize newly cloned objects. This lets me create a scope that the fibmemo block can close over (i.e. it does not see anything outside of this scope—i.e. the outside this object).
Once I had that, it was pretty easy to get rid of the object and make the scope the block closes over a method.
No extra identifiers in the global top-level namespace :).
You may also notice the call to setIsActivatable(true). Another difference between blocks and methods is that blocks are not activatable by default, that is referring to them does not activate them. I.e. aBlock() will not execute the block while aMethod() will. One would need to use aBlock call()to actually execute the block (the parens are optional of course). We can make the blocks activatable to make calling them look the same as methods.
If you have programmed in Javascript you may notice that these approaches are very javascripty (maybe even 'Crockfordian'), I don't necessarily feel that the above is good or idiomatic Io (though nothing may be wrong with it either). Io is very comfortable with its objects and messages and would probably have me just call fakeCloure fibmemo(20) (and to stop calling it fakeClosure). The attempt to call this method from the global object even though its data is not visible in the global namespace was really a yak shaving exercise that I was happy to pursue simply to explore the slots, scopes, blocks and methods that Io provides.