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

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.