At 11:46 PM 1/30/2003 -0500, Anthony Hannan wrote:
... Allen Wirfs-Brock Allen_Wirfs-Brock@Instantiations.com wrote:
The topic at hand is dynamic "variable" scoping.
I prefer messages to variables because they are more powerful and general. It allows more sophisticated queries, instead of just returning a fixed value. I guess I'm just proposing another form of exception handling, where exceptions are distinguished by selector instead of class, that is why I reused #on:do:.
I even like to think of global variables as messages sent to Smalltalk or the class's environment/pool. Like Self, no variables just objects and messages.
The reason I quoted "variable" above is because the majority of the recent discussions and proposals concerning "dynamic scoping" have not been about scoping actual variable references but about a mechanism and message protocol for creating a dynamically scoped name/value association context. Almost all the proposals I have seen use some message protocol to accomplish this. The only alternative would be to change the Smalltalk language (and hence compiler's) definition of variable reference such that they are dynamically rather than lexically scoped. For now, I will simply assert that this would be a bad idea.
Something you may want to consider is what constitutes the difference between a language extension and a new language. A good language extension should add the desired functionality in a way that is both program and programmer compatible with the existing language. In the absence of it's use, an extension shouldn't change the behavior of any existing programs and probably even more importantly, it shouldn't change any programmer's conceptual understanding of the language. If an extension doesn't have these characteristics then what you are actually doing is defining a new language. This may be appropriate in some circumstances but if that is what you are doing you need to be up front about it. "Self" is a Smalltalk-like language but it is clearly a different language. If you want to define a new Smalltalk-like language that is your prerogative, but don't try to sneak it in as part of a focused functional extension to the existing language.
You still seem to want to have some sort of commonality between exception handling and dynamic "variable" scoping. I suspect this is your language implementor persona getting in the way of your language designer persona. Clearly, exception handlers use a dynamic scoping mechanism and it has been demonstrated that dynamically scoped name/value associations (fluids would be a fine shorthand for that phrase) can be implemented using the Smalltalk exception mechanism. However, that doesn't mean that exceptions and fluids are the same thing. I assert that a programmer who needs to establish an exception handling context is thinking about something very different from a programmer who needs to establish a fluid binding context. You will only create confusion, if you try to make them look the same.
Equating exceptions and fluids would also unnecessarily limit your implementation alternatives. It would not be at all surprising to discover that the usage patterns of exceptions and fluids are quite different and call for differing optimization strategies. For example, you might discover that "shallowing bind" mechanisms are better suited for one and "deep binding" mechanisms for the other.
Another good language design rule is to optimize the design of a feature for its most common usage. In this case I'm pretty confident that the most common usage would be a fixed value binding. While an "active value" (a block evaluation) is more powerful, it isn't going to be the most common usage. By requiring a block for the bound value you are complicating the most common usage and introducing likely error scenarios that would not exist for fixed values. It's fine to provide an alternative "active value" form but don't force everybody to use it all the time.
To satisfy your design principles, we could change the syntax to:
foo Context bind: #world toDo: [myWorld] during: [self bar].
bar Context world.
Context would be an object that only understands #bind:toDo:during: and #doesNotUnderstand:. #doesNotUnderstand: would search the sender chain for the first #bind:toDo:during: context for the message's selector and execute its toDo: block. I believe this syntax satisfies the design principles you mention below.
Aside from the block as the second argument, my main concern is the #doesNotUnderstand: trickery. Another good Smalltalk design principle is: don't use #doesNotUnderstand: to create surface syntax. The virtual machine implementor will have made optimization trade-offs based upon observed or projected usage of #doesNotUnderstand:. What if the usage patterns of fluids are very different from those and suggest the need of a higher level of optimization? In your design, you don't have a local optimization alternative. You are going to have to tackle optimizing #doesNotUnderstand:.
So, my preference (using this vocabulary) would be:
foo Context bind: #world to: myWorld during: [self bar]. "ok to keep #bind:toDo:during: as an alternative"
bar Context bindingOf: #world.
If your concern is the verbosity of #bindingOf: I would go with the symbol based protocol:
foo #world bindTo: myWorld during: [self bar].
bar #world binding
I suspect that the verbosity is probably not really a big issue as I suspect that most of these binding should be encapsulated in other methods. For example you might expect Morphic class to define:
use: aWorld during: aBlock ^Context bind: #world to: aWorld during: aBlock.
currentWorld |w| w := Context bindingOf: #world. w ~~ nil ifTrue: [^w] ifFalse: [^World]
Allen_Wirfs-Brock@Instantiations.com
squeak-dev@lists.squeakfoundation.org