Next: Disk file-IO, Previous: Regular expressions, Up: Features
[This section (and the implementation of namespaces in GNU Smalltalk) is based on the paper Structured Symbolic Name Spaces in Smalltalk, by Augustin Mrazik.]
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:
An expression in the top level of a code body, possibly with special syntax available such as assignment or branching.
A customer’s trace report of recent transactions.
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.
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
Association
s 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:
.
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.
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):
#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.
#do:
and #keys
This should return all the objects in the namespace, including those which are inherited.
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
.
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 Array
s, String
s, 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 LargeInteger
s 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: Disk file-IO, Previous: Regular expressions, Up: Features