Skip to content

Import/Export and the Wealth of Namespaces

Vector 13:3, page 77

language design

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 77

Abstract

This article is a proposal for a mechanism to reference objects in namespaces as local variables which are not erased on exit. The connection of unqualified object names to namespace objects of the same names is defined as a ⎕ function. The connection is localized at the level of the function which makes the connection. The value of the mechanism for object oriented programming is explored. Two advantages of such a scheme, inheritance without duplication and "fire and forget," are discussed. Several other new ⎕ functions and variables are suggested as convenient to the basic proposal.

What’s in a Namespace?

Namespaces are a concept recently introduced in Dyalog APL. They are named objects which contain a collection of APL functions and variables. This grouping is self contained, allowing objects with duplicate names to exist in separate namespaces.

Classical APL has only one namespace, the global workspace. Schemes which allowed grouping objects in the past sometimes allowed objects to be packed into packages. However, these objects were packed from and unpacked into the global workspace.

Multiple namespaces behave somewhat like multiple workspaces in that object names can be duplicated. The difference is that multiple namespaces can exist in a single workspace. The root namespace is defined as the classical APL global workspace. Just as the root namespace can contain "children" namespaces, namespaces contained in the root can themselves contain children namespaces.

Namespace Notation

The specific notation for collecting objects into namespaces is not discussed, mainly because I know only what I read in Vector about them. I have had no direct contact with them. I will define "fully qualified" object names as names which show their descent from the root namespace. If an object named OBJ1 is contained in a namespace called NAME1 in the root namespace, then its fully qualified name is #.NAME1.OBJ1. By convention, "#" is the root namespace.

Importing Namespace Objects

The basic proposal is to define a ⎕ function, ⎕IMP, which takes a character vector containing the name of a namespace as its right argument. When executed inside a function, ⎕IMP would localize all names of objects in the namespace. However, instead of making the names local variables with no initial values, ⎕IMP would instead link all local references to these objects with the namespace objects. Any reference to names of objects in the namespace would use and change the namespace objects. In effect, objects in the namespace would be passed to the function by reference rather than value.

After the function exit, the local link to the namespace objects would be dissolved, returning to the conditions prior to the function call. Ordinary local variables would be erased. Whatever global and local variables were in use in the calling function would be restored to view. However, changes to namespace objects would be permanently retained in the namespace.

For example, if OBJ1 is the name both of an object in the root and in namespace NAME1, then a function call of ⎕IMP '#.NAME1' would make all subsequent references in the calling function to OBJ1 a reference to #.NAME1.OBJ1 and not to #.OBJ1 in the global workspace.

Under this definition of ⎕IMP, calling it twice would localize and link the objects in a second namespace. This localization would supersede previous localizations. For example, if NAME2 is a namespace containing an OBJ1 of its own, then executing ⎕IMP '#.NAME1' ⎕IMP '#.NAME2' would cause references to an unqualified OBJ1 to refer to #.NAME2.OBJ1 and instead of either #.NAME1.OBJ1 or #.OBJ1.

As a convenience, I will expand the definition of ⎕IMP to allow a list of namespaces as a right argument. For example, ⎕IMP '#.NAME1 #.NAME2' would have the same effect as first doing ⎕IMP '#.NAME1' and then ⎕IMP '#.NAME2' in that order.

Sins Visited Upon the Children?

Since namespaces can themselves contain namespaces, it seems logical to define the contained namespaces as children. I’m not sure how to handle inheritance, that is whether objects in the parent namespaces are visible to the children. There are two alternatives: 1) children inherit all objects from their ancestors or 2) children inherit nothing from their ancestors unless specified explicitly. Let’s examine the consequences of each approach.

If child namespaces inherit all objects from their ancestors, then all objects contained in any ancestor namespace would be visible to the child. In this case, the effect of doing ⎕IMP '#.GRANNY.DAD.KID' would have the same effect as ⎕IMP '#.GRANNY' ⎕IMP '#.GRANNY.DAD' ⎕IMP '#.GRANNY.DAD.KID'. The reader should note that inheritance would extend to ALL generations, not merely the biblical 3 or 4 generations. By implication, all the objects in the root namespace would be visible to all other namespaces as long as they had unqualified names distinct from unqualified object names in the child namespaces.

The alternative would be to inherit nothing. This option would mean that doing ⎕IMP '#.GRANNY.DAD.KID' would make visible only objects in the KID namespace. Objects in GRANNY and DAD would not be visible unless you imported them. To get inheritance from GRANNY and DAD you would have to import them explicitly: ⎕IMP '#.GRANNY #.GRANNY.DAD #.GRANNY.DAD.KID'. The implication for the root namespace is that you would have to import it explicitly as well. Root objects perhaps might be defined as visible for functions with no ⎕IMP calls, but would have to be explicitly imported if ⎕IMP was called.

Free to Choose

To summarize, inheriting everything has the advantage of requiring less code, but giving less control to the programmer. Inheriting nothing means that you have more control, but have to tediously enumerate all importations at each function level. Traditional APL treatment of local variables as visible to all functions called by the localizing function, which I refer to as global presumption, would tend to argue for inheriting everything. Current fashion in other languages would tend to argue for inheriting nothing automatically.

I think we should choose to inherit everything because it’s more consistent with traditional APL treatment of local variables. However, I am not too strong in this conviction. Switching to political mode, I’ve invented a way to have it both ways, so you are free to choose whichever way you want to go.

Exclusive Locales

I will define a new ⎕ variable, ⎕XLOC. It will have two allowed values, 0 and 1. If it’s 1, all variable references are assumed to be exclusively local. Any global objects, in the root namespace or anywhere else, have to be explicitly imported, with no automatic inheritance. Any object that’s not imported is local exclusively to the function using it. This means that any function you want to use inside another function has to be imported first. Conceptually, this means each function is an island to itself, with a wall around it. The traditional APL global presumption is reversed if ⎕XLOC is 1. Computer science purists’ heaven, where everything has to be declared, or in this case imported, exists when ⎕XLOC is 1.

The default value of ⎕XLOC should be 0. If ⎕XLOC is 0, then traditional APL’s global presumption prevails, and inheritance is automatic.

⎕XLOC should be localizable and eligible for inclusion in namespaces. Whenever the value of ⎕XLOC changes, the effect should be applied to the currently executing function. In other words, if you localized ⎕XLOC and then set it to 1 in function PURE1, then all global objects disappear for PURE1 as well as for functions called by PURE1. Importations into PURE1 would be in the inherit nothing mode. If PURE1 called function SLOP1 and SLOP1 localized ⎕XLOC and set it to 0, then SLOP1 would see all objects imported or created by PURE1. Imports in SLOP1 would be in the inherit everything mode. However, if SLOP1 had no imported namespaces, then SLOP1 would not see the root namespace or anything created or imported by functions before PURE1 was called.

If ⎕XLOC is 1, then it would make sense to allow importation of individual objects as well as whole namespaces. For example, if OBJ1 is an object in namespace #.NAME1, then ⎕IMP '#.NAME1.OBJ1' would import just OBJ1 and nothing else from namespace NAME1. On the other hand, if ⎕XLOC is 0 then it makes no sense to import a specific object. With automatic inheritance, ⎕IMP '#.NAME1.OBJ1' would import all of the objects in NAME1, as well as all the objects in root.

Using ⎕XLOC, programmers could choose their poison: purist heaven where you call every shot, or sloppy world where any landing you can walk away from is a good landing. They could even switch back and forth to suit their convenience.

What’s Your Name, Who’s Your Daddy?

If a lot of namespaces are imported, programmers like the author will get confused and need to find out the fully qualified name of any object. I think we will need a ⎕WHO with a right argument of a list of object names. It could return the fully qualified names of the objects in the list. For example, if OBJ1 currently refers to an object in namespace NAME1 and OBJ2 refers to a root object, then ⎕WHO 'OBJ1 OBJ2' would return '#.NAME1.OBJ1 #.OBJ2' as a character result.

Programmers might also want to see which importations have been made at each level of the SI stack. Perhaps ⎕SINS could be defined as a function to show the SI stack with imported namespaces, just as ⎕SINL shows local variables. Those without ⎕SINS could throw the first stones.

Setting a Default Namespace

People being basically lazy, they will want a way to set a default namespace just like they can set a default working file directory. I think ⎕BNS should be defined as a localizable variable containing a character vector name of the base namespace. The default value of ⎕BNS should be the root namespace '#'. The way this would work is as follows. If ⎕BNS is '#.NAME1' then ⎕IMP'NAME2' would have the same effect as ⎕IMP '#.NAME1.NAME2'.

Where Have All My Objects Gone?

One problem yet to be faced is where to put global objects created by functions who have imported namespaces. Should they go to root or to the last imported namespace? I think they should go to the base namespace in ⎕BNS. If ⎕BNS is '#.NAME1' and a previously undefined object NEW1 is given a value, then since NEW1 does not have a fully qualified name it is given the fully qualified name of #.NAME1.NEW1. Note that globals are created only when ⎕XLOC is 0. When ⎕XLOC is 1, all variables are assumed to be local.

Note that under the default values for ⎕XLOC of 0 and for ⎕BNS of '#' that the behaviour is exactly the same as classical APL. All references to unqualified global names are references to the root namespace, that is the workspace itself. All global objects created by a function are placed in the root namespace. Unless you create namespaces and import them, or change the values of ⎕XLOC or ⎕BNS, nothing has changed.

Object Oriented APL

Why go to all this trouble? The main reason to consider it is that the rest of the world has become object oriented. While APL remains a classless language, lacking any definition of object classes, everybody else has gotten very class conscious. Basic now has visible object features and C has improved its grade to C++. Recently, a computer science professor I met socially thought object oriented programming in APL was a really good joke. Face it, the world thinks we have no class. I think this proposal is a way to be taken more seriously. Specifically, this approach accommodates the encapsulation of objects, of APL functions and variables, required to do object oriented programming. Conceptually it works in a way that’s not a violent departure from "classical" APL. The defaults are designed in such a way that classical APL behaviour is unchanged if the new features are not used.

Inheritance Without Duplication

There are major advantages to this approach. The first benefit is inheritance without duplication. Object oriented programming requires inheriting properties from parent objects. Using importation, inheritance can be arranged, without duplication, under function control. Assume that #.GPBUTTON is a namespace containing objects for a general purpose button, and #.SPBUTTON is a namespace containing only the objects related to a specific button. Then ⎕IMP '#.GPBUTTON #.SPBUTTON' would give access to all the general purpose button objects and all of the specific button’s objects. The namespace SPBUTTON would need to contain only objects which were unique to it. All the generic objects in GPBUTTON could be shared by every button by importing GPBUTTON before importing each specific button’s namespace. Further, if objects in SPBUTTON had the same names as objects in GPBUTTON, all unqualified references would use the specific objects from SPBUTTON. Even functions from GPBUTTON would use SPBUTTON objects when called, as long as the functions had ⎕XLOC of 0 when called.

In contrast, the scheme explained in Vector seems to allow only one namespace to be active at a time. As a result of this limitation, if you want to have one namespace inherit from another you have to copy the contents from one namespace to another, or you have to refer to the inherited objects using their fully qualified names.

Fire and Forget

The second benefit is "fire and forget," a concept borrowed from military smart bombs. In APL, this would mean that whatever namespaces are used by functions you call, the active namespaces for the calling function are the same after the call as before. The calling function can "fire" functions, which import whatever namespaces and objects they need, and "forget" about what namespaces they use because, on return, imported namespaces are returned to what they were before.

In the scheme for namespaces described in Vector, you can get a fire and forget effect if you call a function in another namespace using the function’s fully qualified name. However, using the fully qualified name of the function puts you in its namespace. You have to pass all objects from other namespaces to the function as arguments or refer to the objects inside the function using fully qualified names. If you want your function to change foreign namespace variables and have the name of the namespace as an argument, the function will have to change namespaces or execute strings with the fully qualified names to work.

Let me explain the problem with an example. Suppose #.GPBUTTON.CLICK is a function that handles button clicks. It handles button clicks for a lot of specific buttons, including the specific button represented by objects in namespace #.GPBUTTON.SPBUTTON. CLICK needs to set variables in SPBUTTON in response to the click. CLICK can’t refer to the variables it needs to set using fully qualified names because it’s a utility function for a lot of buttons. CLICK either can execute strings with a variable containing '#.GPBUTTON.SPBUTTON.' put in front of every variable name or it can change the active namespace to #.GPBUTTON.SPBUTTON. The executions are ugly and time-consuming, but changing the namespace makes fire and forget a problem.

As I understand the current scheme, namespace activations are a global effect, good forever. If a function changes the active namespace, then the new namespace remains active even after the function that changed it returns. Further, the new namespace remains active until the next explicit change. If a called function, like CLICK, needs to change the active namespace, the called function also needs to change the namespace explicitly back to the way it was before it returns. Otherwise, the calling function has to check after each function call to see if the active namespace is still what’s expected.

In contrast, the effects of ⎕IMP are defined as local. Any called function can use whatever namespaces it needs, without having to explicitly clean up after itself. The calling function retains its imported namespaces automatically. Programmers can fire functions and forget about what namespaces they are going to use and change.

Jus’ Like the Rich Folks

Another benefit of this proposal is that it answers computer science purists’ complaints about the scope of local variables in APL. In most of the languages beloved by the purists, the scope of local variables is strictly the function they are declared in. Purists detest the global presumption of classical APL, that all local variables declared in a calling function, as well as global variables, are visible as globals to all functions subsequently called. It bothers them almost as much as naked GO TO’s. We seem to be in the process of getting rid of GO TO’s, so we might as well work on getting around the global presumption as well. Using this mechanism, we can force everything to be explicitly imported, behaving "jus’ like the rich folks" want us to behave. And in the privacy of our own code, we can set ⎕XLOC back to 0 and do what we want.

Caveats

You should remember that I have had no direct contact with namespace implementations. I literally know only what I read in Vector in the articles listed below. I may not have understood them correctly, and if so then that’s my fault entirely. I have tried to present my scheme as an independent view of how to handle namespaces, hoping you can forgive any mistakes in my comments on the current implementation.

Also, I feel it’s fair to tell you that I am not working for any APL language implementor in any capacity, so I can’t make them read this, let alone implement it. I do have a lot of practical programming experience in APL, which I first learned in 1968. This mechanism would be extremely useful in work I’m doing right now. If this mechanism were implemented right now, I would be using it.

Douglas R. Bohrer
Data Processing Manager
Consulting Services
EVEREN Securities Inc.

Bibliography

Peter Donnelly, "The Use of Namespaces for Encapsulation – a Practical Introduction," Vector, Vol.11 No.3, January, 1995, pp.66-74.

Kimmo Kekäläinen, "Namespaces: A Way to a Well Organized World or Just Another Means to Multiply your Chaos," Vector, Vol.11 No.3, January, 1995, pp.92-95

Eric Lescasse, "Namespaces," Vector, Vol.11 No.3, January, 1995, pp.75-91