Symbols and Namespaces

With transient symbols we do already have a certain kind of namespaces, implicit by the lexical (source file) context. So I decided to go ahead, and implement "named" symbol namespaces... if we regard transient symbols as residing in their own anonymous namespaces.

This feature is available only in the 64-bit version. It could perhaps be implemented also in Ersatz and MiniPicoLisp, but unfortunately not in the 32-bit version, because of its different symbol table mechanism.

The underlying concepts are very simple. Only two new function are really needed, 'symbols' and 'local', as well as a new global 'pico' holding the initial (default) symbol namespace.

Additional helper functions include 'nsp' and 'import'.

If symbols is never called, everything will work as before. You can go on merrily programming PicoLisp just as you were.

symbols

Calling it without arguments
   : (symbols)
   -> (pico)
returns the current list of namespaces. Here it means that only the namespace pico is searched by the reader.

If you call it with the name of a new namespace, followed by the name(s) of existing namespaces
   : (symbols 'myNames 'pico)
   -> (pico)
   myNames:  # prompt has changed to current namespace
the following happens: The symbol 'myNames' (existing in the current namespace) is initialized as a new namespace if it wasn't already, and the current search order is set to (myNames pico.

This means that the reader will look up symbols first in myNames and - if not found - in pico.
   myNames: (symbols)
   -> (myNames pico)
Now new symbols will be added to myNames.

A symbol will not be garbage collected as long as it is referred to from at least one namespace. Symbols from other namespaces, when visible in data structures, will appear as transient symbols.

If myNames were a library, it is advisable to ensure that its symbols are created here. This is done by calling (local) followed by a single symbol or a list of symbols. Note that this is an unusual semantics for Lisp: The "arguments" to local are not passed the normal way, but in the input stream following the (local) call, so that the reader can handle them in the new namespace.

Fiddling around with symbols named "Foo":
   myNames: (local) Foo  # Create 'Foo' locally
   myNames: (zero Foo)   # Set it to zero
   -> 0

   : Foo                  # check
   -> 0

   : (symbols '(pico))    # switch namespace
   -> (myNames pico)

   : Foo
   -> NIL                 # 'Foo' was created or found in 'pico'

   : (symbols '(myNames pico))
   -> (pico)
   myNames: Foo           # Here 'Foo' is the one with value 0 again
   -> 0


Accessing Symbols in Another Namespace

To access a symbol in another namespace, we can use a tilde character separating a namespace from a symbol. The tilde was chosen because it possibly does not conflict with the rest of the PicoLisp syntax.
   : Foo
   -> NIL

   : myNames~Foo
   -> 0

   : (setq myNames~Bar 'myNames~Foo)  # Create a new symbol 'Bar' in 'myNames
   -> "Foo"  # 'Foo' is transient in 'pico'

   : Bar
   -> NIL

   : myNames~Bar
   -> "Foo"

   : (symbols '(myNames pico))
   -> (pico)

   myNames: Bar
   -> Foo
This syntax can also be chained, e.g. myNames~yourNames~foo.

All of this is a simple extension of the general principles underlying PicoLisp symbols.

Until now, a symbol was either interned or not interned into the global symbol table (if we ignore external symbols for the moment, and regard transient symbols as a special way of implicit interning / uninterning a symbol).

Now, with the 'symbols' extension, there may be several global symbol tables, with a list of them active at a given time. Thus, a symbol may be either non-interned, or interned in one or several symbol tables.

Importing Symbols

Importing symbol from another namespace can be done with 'import', a simple frontend to 'intern'.
   myNames: (symbols '(pico))  # back to the default namespace
   -> (myNames pico)

   (import myNames~Foo myNames~Bar)
   -> (Foo "Bar")

   : Foo
   -> 0

   : Bar
   -> NIL

   : (== 'Foo 'myNames~Foo)
   -> T
This will make the symbols 'Foo' and 'Bar' from 'myNames' available as internal symbols in 'pico'.

Practical Considerations

For using namespaces in libraries and applications, the following strategy turned out as the best:

In a library, initialize the new namespace as the first one in the search order near the beginning of the source file, and then take care to localize every symbol that should be local in this package before it appears the first time.

For example, in file "@lib/gis.l":
   (symbols 'gis 'pico)
   ...
   (local) nm
   (de nm (Lat Lon)
      ...
   (local) [lat lon fmt]
   (de lat (Lat)
      ...
   (de lon (Lon)
      ...
   (de fmt (Lat Str Lon)
      ...
   # Short distance, assuming flat earth
   (local) distance
   (de distance (Lat1 Lon1 Lat2 Lon2)  # [m]
      ...
and in file "@lib/android.l":
   (symbols 'android 'pico)
   ...
   # (boss 'sym ['any ..])
   (local) boss
   (de boss @
      ...
   # Android Context
   (local) CONTEXT
   (de CONTEXT . {OOOO40000000000})
   ...
   # (java "cls") -> cls                    Get class
   (local) [java1 java *Java *Lisp]
   (de java1 ()
      ...
   (de java @
      ...
   
Then, in the application code, set the search order so that 'pico' is searched first, followed by the used libraries:
   (symbols '(pico gis android))
In this way, most symbols in the program can be referred to without any explicit namespace specifier.

https://picolisp.com/wiki/?symbolnamespaces

17may18    abu
Revision History