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)
... )
...
http://picolisp.com/wiki/?transientnamespaces
| 17sep17 | abu |
