Transient Namespaces

Note: This article is obsolete, since namespace semantics changed with picoLisp-17.6

A few days ago I hit upon an interesting concept: Transient Namespaces.

With Transient Namespaces I do not mean namespaces for transient symbols (there is always only one single namespace for transient symbols at any moment), but to use a transient symbol (instead of an internal symbol) for a temporary namespace.

It turns out that this makes quite some sense.

Last week I added an example for the Common-Lisp-style DO* function to https://software-lab.de/doc/faq.html#macros
   (de do* "Args"
      (bind (mapcar car (car "Args"))
         (for "A" (car "Args")
            (set (car "A") (eval (cadr "A"))) )
         (until (eval (caadr "Args"))
            (run (cddr "Args"))
            (for "A" (car "Args")
               (and (cddr "A") (set (car "A") (run @))) ) )
         (run (cdadr "Args")) ) )
It can be used as
   : (do* ((A 123) (B 'abc) (C 1 (inc C)))
      ((> C 7) (pack A B))
      (println C) )
   1
   2
   3
   4
   5
   6
   7
   -> "123abc"
As you see, the above implementation of do* uses transient symbols for "A" and "Args". This is the recommended way, to avoid symbol conflicts ("captures" in CL-terminology).

An implementation with internal symbols
   (de do* Args
      (bind (mapcar car (car Args))
         (for A (car Args)
            (set (car A) (eval (cadr A))) )
         (until (eval (caadr Args))
            (run (cddr Args))
            (for A (car Args)
               (and (cddr A) (set (car A) (run @))) ) )
         (run (cdadr Args)) ) )
looks better, but gives a wrong result: Because the symbol A is captured, the function returns "abc" instead of "123abc".

So it is wise to use transient symbols, as in the first version.

However, as PicoLisp has namespaces for internal symbols (in the 64-bit version since 3.0.7.9, and in Ersatz PicoLisp since 3.1.0.10), there is a third way:

Define do* in a separate namespace, and keep A and Args locally:
   (symbols 'flow 'pico)

   (local Args A)

   (de pico~do* Args
      (bind (mapcar car (car Args))
         (for A (car Args)
            (set (car A) (eval (cadr A))) )
         (until (eval (caadr Args))
            (run (cddr Args))
            (for A (car Args)
               (and (cddr A) (set (car A) (run @))) ) )
         (run (cdadr Args)) ) )
The 'symbols' call in (symbols 'flow 'pico) creates a new namespace flow as a copy of the pico namespace, and activates it.

(local Args A) ensures that Args and A are local to the new namespace.

pico~do* refers to the existing or newly created symbol do* in the pico namespace.

Now, back in the pico namespace (e.g. after hitting EOF), do* is available
   : (pp 'do*)
   (de do* "Args"
      (bind (mapcar car (car "Args"))
         (for "A" (car "Args")
            (set (car "A") (eval (cadr "A"))) )
         (until (eval (caadr "Args"))
            (run (cddr "Args"))
            (for "A" (car "Args")
               (and (cddr "A") (set (car "A") (run @))) ) )
         (run (cdadr "Args")) ) )
   -> do*
As you see, "Args" and "A" are transient symbols relative to the pico namespace, as the symbols Args and A are internal to the flow namespace.

There is just one serious drawback to this approach. The call
   (symbols 'flow 'pico)
creates the new namespace flow as a copy of pico. For a typical namespace, this eats up about 25 kB of memory. If you get into the habit of creating a lot of such namespaces, it could become quite wasteful.

Here Transient Namespaces come into play: Just create the namespace as
   (symbols "flow" 'pico)
i.e. use the transient symbol "flow" instead of the internal symbol flow. While it still creates a full copy of pico, it will become garbage as soon as "flow" goes out of scope, and no space is wasted in the long term!

Normally, you would create a library for such flow functions in a separate source file, containing more than a single function definition. To be sure to avoid symbol conflicts also between those function, you may use more than one call to local (analog to '====' calls for separating transient symbols):
   (symbols "flow" 'pico)

   (local Args A)

   (de pico~do* Args
      (bind (mapcar car (car Args))
         (for A (car Args)
            (set (car A) (eval (cadr A))) )
         (until (eval (caadr Args))
            (run (cddr Args))
            (for A (car Args)
               (and (cddr A) (set (car A) (run @))) ) )
         (run (cdadr Args)) ) )

   (local X Y Prg)

   (do pico~mumble (X Y . Prg)
      ... )

   ...

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

17sep17    abu