Next: , Previous: , Up: Features  


2.3 Namespaces

[This section (and the implementation of namespaces in GNU Smalltalk) is based on the paper Structured Symbolic Name Spaces in Smalltalk, by Augustin Mrazik.]

2.3.1 Introduction

The Smalltalk-80 programming environment, upon which GNU Smalltalk is historically based, supports symbolic identification of objects in one global namespace—in the Smalltalk system dictionary. This means that each global variable in the system has its unique name which is used for symbolic identification of the particular object in the source code (e.g. in expressions or methods). The most important of these global variables are classes defining the behavior of objects.

In development dealing with modelling of real systems, polymorphic symbolic identification is often needed. By this, we mean that it should be possible to use the same name for different classes or other global variables. Selection of the proper variable binding should be context-specific. By way of illustration, let us consider class Statement as an example which would mean totally different things in different domains:

GNU Smalltalk or other programming language

An expression in the top level of a code body, possibly with special syntax available such as assignment or branching.

Bank

A customer’s trace report of recent transactions.

AI, logical derivation

An assertion of a truth within a logical system.

This issue becomes inevitable if we start to work persistently, using ObjectMemory snapshot to save after each session for later resumption. For example, you might have the class Statement already in your image with the “Bank” meaning above (e.g. in the live bank support systems we all run in our images) and you might decide to start developing YAC [Yet Another C]. Upon starting to write parse nodes for the compiler, you would find that #Statement is boundk in the banking package. You could replace it with your parse node class, and the bank’s Statement could remain in the system as an unbound class with full functionality; however, it could not be accessed anymore at the symbolic level in the source code. Whether this would be a problem or not would depend on whether any of the bank’s code refers to the class Statement, and when these references occur.

Objects which have to be identified in source code by their names are included in Smalltalk, the sole instance of SystemDictionary. Such objects may be identified simply by writing their names as you would any variable names. The code is compiled in the default environment, and if the variable is found in Smalltalk, without being shadowed by a class pool or local variables, its value is retrieved and used as the value of the expression. In this way Smalltalk represents the sole symbolic namespace. In the following text the symbolic namespace, as a concept, will be called simply environment to make the text more clear.

2.3.2 Concepts

To support polymorphic symbolical identification several environments will be needed. The same name may exist concurrently in several environments as a key, pointing to diverse objects in each.

Symbolic navigation between these environments is needed. Before approaching the problem of the syntax and semantics to be implemented, we have to decide on structural relations to be established between environments.

Since the environment must first be symbolically identified to direct access to its global variables, it must first itself be a global variable in another environment. Smalltalk is a great choice for the root environment, from which selection of other environments and their variables begins. From Smalltalk some of the existing sub-environments may be seen; from these other sub-environments may be seen, etc. This means that environments represent nodes in a graph where symbolic selections from one environment to another one represent branches.

The symbolic identification should be unambiguous, although it will be polymorphic. This is why we should avoid cycles in the environment graph. Cycles in the graph could cause also other problems in the implementation, e.g. inability to use trivially recursive algorithms. Thus, in general, the environments must build a directed acyclic graph; GNU Smalltalk currently limits this to an n-ary tree, with the extra feature that environments can be used as pool dictionaries.

Let us call the partial ordering relation which occurs between environments inheritance. Sub-environments inherit from their super-environments. The feature of inheritance in the meaning of object-orientation is associated with this relation: all associations of the super-environment are valid also in its sub-environments, unless they are locally redefined in the sub-environment.

A super-environment includes all its sub-enviroments as Associations under their names. The sub-environment includes its super-environment under the symbol #Super. Most environments inherit from Smalltalk, the standard root environment, but they are not required to do so; this is similar to how most classes derive from Object, yet one can derive a class directly from nil. Since they all inherit Smalltalk’s global variables, it is not necessary to define Smalltalk as pointing to Smalltalk’s Smalltalk in each environment.

The inheritance links to the super-environments are used in the lookup for a potentially inherited global variable. This includes lookups by a compiler searching for a variable binding and lookups via methods such as #at: and #includesKey:.

2.3.3 Syntax

Global objects of an environment, be they local or inherited, may be referenced by their symbol variable names used in the source code, e.g.

            John goHome

if the #John -> aMan association exists in the particular environment or one of its super-environments, all along the way to the root environment.

If an object must be referenced from another environment (i.e. which is not one of its sub-environments) it has to be referenced either relatively to the position of the current environment, using the Super symbol, or absolutely, using the “full pathname” of the object, navigating from the tree root (usually Smalltalk) through the tree of sub-environments.

For the identification of global objects in another environment, we use a “pathname” of symbols. The symbols are separated by periods; the “look” to appear is that of

Smalltalk.Tasks.MyTask

and of

Super.Super.Peter.

As is custom in Smalltalk, we are reminded by capitalization that we are accessing global objects. Another syntax returns the variable binding, the Association for a particular global. The first example above is equivalently:

#{Smalltalk.Tasks.MyTask} value

The latter syntax, a variable binding, is also valid inside literal arrays.

2.3.4 Implementation

A superclass of SystemDictionary called RootNamespace is defined, and many of the features of the Smalltalk-80 SystemDictionary will be hosted by that class. Namespace and RootNamespace are in turn subclasses of AbstractNamespace.

To handle inheritance, the following methods have to be defined or redefined in Namespace (not in RootNamespace):

Accessors like #at:ifAbsent: and #includesKey:

Inheritance must be implemented. When Namespace, trying to read a variable, finds an association in its own dictionary or a super-environment dictionary, it uses that; for Dictionary’s writes and when a new association must be created, Namespace creates it in its own dictionary. There are special methods like #set:to: for cases in which you want to modify a binding in a super-environment if that is the relevant variable’s binding.

Enumerators like #do: and #keys

This should return all the objects in the namespace, including those which are inherited.

Hierarchy access

AbstractNamespace will also implement a new set of methods that allow one to navigate through the namespace hierarchy; these parallel those found in Behavior for the class hierarchy.

The most important task of the Namespace class is to provide organization for the most important global objects in the Smalltalk system—for the classes. This importance becomes even more crucial in a structure of multiple environments intended to change the semantics of code compiled for those classes.

In Smalltalk the classes have the instance variable name which holds the name of the class. Each defined class is included in Smalltalk, or another environment, under this name. In a framework with several environments the class should know the environment in which it has been created and compiled. This is a new property of Class which must be defined and properly used in relevant methods. In the mother environment the class shall be included under its name.

Any class, as with any other object, may be included concurrently in several environments, even under different symbols in the same or in diverse environments. We can consider these “alias names” of the particular class or other value. A class may be referenced under the other names or in other environments than its mother environment, e.g. for the purpose of instance creation or messages to the class, but it should not compile code in these environments, even if this compilation is requested from another environment. If the syntax is not correct in the mother environment, a compilation error occurs. This follows from the existence of class “mother environments”, as a class is responsible for compiling its own methods.

An important issue is also the name of the class answered by the class for the purpose of its identification in diverse tools (e.g. in a browser). This must be changed to reflect the environment in which it is shown, i.e. the method ‘nameIn: environment’ must be implemented and used in proper places.

Other changes must be made to the Smalltalk system to achieve the full functionality of structured environments. In particular, changes have to be made to the behavior classes, the user interface, the compiler, and a few classes supporting persistance. One small detail of note is that evaluation in the REPL or ‘Workspace’, implemented by compiling methods on UndefinedObject, make more sense if UndefinedObject’s environment is the “current environment” as reachable by Namespace current, even though its mother environment by any other sensibility is Smalltalk.

2.3.5 Using namespaces

Using namespaces is often merely a matter of adding a ‘namespace’ option to the GNU Smalltalk XML package description used by PackageLoader, or wrapping your code like this:

    Namespace current: NewNS [
        
    ]

Namespaces can be imported into classes like this:

    Stream subclass: EncodedStream [
        <import: Encoders>
    ]

Alternatively, paths to classes (and other objects) in the namespaces will have to be specified completely. Importing a namespace into a class is similar to C++’s using namespace declaration within the class proper’s definition.

Finally, be careful when working with fundamental system classes. Although you can use code like

    Namespace current: NewNS [
        Smalltalk.Set subclass: Set [
            <category: 'My application-Extensions'>
            
        ]
    ]

this approach won’t work when applied to core classes. For example, you might be successful with a Set or WriteStream object, but subclassing SmallInteger this way can bite you in strange ways: integer literals will still belong to the Smalltalk dictionary’s version of the class (this holds for Arrays, Strings, etc. too), primitive operations will still answer standard Smalltalk SmallIntegers, and so on. Similarly, word-shaped will recognize 32-bit Smalltalk.LargeInteger objects, but not LargeIntegers belonging to your own namespace.

Unfortunately, this problem is not easy to solve since Smalltalk has to know the OOPs of determinate class objects for speed—it would not be feasible to lookup the environment to which sender of a message belongs every time the + message was sent to an Integer.

So, GNU Smalltalk namespaces cannot yet solve 100% of the problem of clashes between extensions to a class—for that you’ll still have to rely on prefixes to method names. But they do solve the problem of clashes between class names, or between class names and pool dictionary names.

Namespaces are unrelated from packages; loading a package does not import the corresponding namespace.


Next: , Previous: , Up: Features