PackageDescription: DynamicBindings

Dynamic Bindings

Last published: August 17, 2004 by 'svpair'

Defines 3 Classes
Extends 7 Classes

DynamicBindings for VisualWorks
by Stephen Pair

===== Introduction =====
DynamicBindings provides a mechanism to bind variable names to values based on a runtime context. In situations where you need widespread access to a common variable, but where you need to be able to control the binding of that variable based on the runtime context, dynamic bindings provides a good solution.

An instance of DynamicBindings is just an IdentityDictionary with the additional property that it can have a parent. Lookups will follow the parent chain until a key is found, or the top of the chain has been reached without finding the key.

You may create a hierarchy of DynamicBindings and directly manipulate the keys and values of those instances (just as you would any dictionary). There is a default instance DynamicBindings that you may use to create new children if you like (but you can also create entirely separate hierarchies). You can access this instance with "Bindings default". To force a process to use one of your sets of DynamicBindings, you write code as follows (note, I only use 'PATH' to highlight the similarity with normal OS environment variables):

myBindings := Bindings default newChild.
myBindings at: #PATH put: '/bin:/usr/bin'.
myBindings do:
[Bindings at: #PATH. "-> '/bin:/usr/bin'"
Bindings at: #PATH put: ('/usr/local/bin:', (Bindings at: #PATH)).
Bindings at: #PATH]. "-> '/usr/local/bin:/bin:/usr/bin'"

For convenience, two methods have been added to Object that enable more direct access to bindings. The following example is equivalent to the first:

myBindings := Bindings default newChild.
myBindings at: #PATH put: '/bin:/usr/bin'.
myBindings bindDuring:
[#PATH binding. "-> '/bin:/usr/bin'"
#PATH binding: ('/usr/local/bin:', (#PATH binding)).
#PATH binding]. "-> '/usr/local/bin:/bin:/usr/bin'"

The first line creates a new set of bindings that is a child of the root bindings. Any variables set in the root bindings are also visible in this new child environment.

The second line sets the dynamic variable "PATH".

The third line uses the evaluates the argument block in the context of our new set of bindings.

The fourth line gets the value for the variable "PATH" (which we just set).

The fifth line in the above example modifies the environment variable "PATH", but only for the duration that the enclosing block is active.

Here is another example:

#PATH binding: '/bin'.
Bindings clamp:
[#PATH binding: '/usr/bin'.
#PATH binding]. "-> '/usr/bin'"
#PATH binding. "-> '/bin'"
#PATH removeBinding.

This example shows the use of the #clamp: method to isolate a our dynamic bindings only for the duration of a block. After the block finishes execution, the original set of bindings is restored. The #clamp: method is the most common and recommended way of using dynamic bindings. You should always clamp your bindings context before setting any bindings to ensure that the scope of your changes to the bindings context is controlled.

===== Scoping semantics =====

A dynamic variables' scope is defined at runtime by an enclosing activation context. To locate the active lexicon of dynamic variables, the system follows the activation stack until a context is encountered that defines the active DynamicBindings (this lookup is implemented using the exception handling system). Any changes to the active DynamicBindings are visible in all activation contexts where that set of DynamicBindings is being used (which may include contexts in more than one activation stack).

===== Recommended usage patterns =====

To illustrate common usage examples, we'll invent a scenario where you've implemented a transaction system and need to have a transaction context associated with any given set of operations. The class that embodies a transaction is called "Transaction" and the access method to get the current transaction context is "Transaction class>>current". The getter method is:

Transaction class>>current
^#'Transaction.current' binding ifNil: [Current := Transaction new]

This getter method attempts to locate a binding for the current transaction. If it fails to find a binding, it falls back on a class variable called "Current". The variable name is a Symbol with the class name and selector name separated with a period (in VisualWorks, you might even want to use the class name with its fully qualified name space). The setter method follows:

Transaction class>>current: aTransaction
#'Transaction.current' binding: aTransaction

This setter method simply sets the binding in the current bindings context. Then, to make use of the transaction context, you would write code as follows:

Bindings clamp:
[Transaction current: someTransaction.
self goDoSomething]

Inside the method #goDoSomething, or any method called from goDoSomething, you can reference access the current transaction using "Transaction current".

===== A bit about how it's implemented =====

The bindings for a given method activation context are located using the exception handling mechanism (see BindingsLocator). If a given stack does not have a handler that answers a set of bindings, then the root bindings (DynamicBindings root) are used.

Unlike other implementations, DynamicBindings do not use the activation stack to define the hierarchy of bindings. Instances of DynamicBindings have their own parent instance variable and will locate enclosing variable scopes by following the chain of parents (*not* by looking for enclosing handlers of BindingsLocator). Using this design, we are able to accomodate a broader range of usage scenarios while simultaneously retaining the semantics of the activation stack based hierarchy (using the #clamp: method). There is also a slight performance advantage to using parent instance variables.

In the VisualWorks version, accessing dynamic bindings is optimized by caching the active set of bindings in the Process' environment. However, the authoritative answer for the current bindings context is always found using the BindingsLocator.

===== Debugging issues =====

Stepping through code in the debugger will properly bind dynamically bound variables. However, if you evaluate code while in the debugger, ordinarily the context stack that evaluates the code will not have the same dynamic bindings context as the stack that is being debugged. To alleviate this inconvenience, an instance method called #bindingsDo: has been added to the Context class and the method ParagraphEditor>>evaluateCompiled: has been modified to make use of that method.

===== Release History =====
Version 1.2 (VisualWorks):
- Initial VW port

Version 1.1 (Squeak):
- Packaged using KomPackaging (Squeak version only)
- Made Object>>binding: answer its argument
- Cleaned up a few obsolete methods from the old RuntimeEnvironments implementation

Version 1.0 (Squeak):
- Initial release