Fake Emacs Namespaces
Back in May I wrote a crude
defpackage function for Elisp, modeled
after Common Lisp’s version. I’m calling them fakespaces.
It works like so (see
example.el for detailed information on this
(require 'fakespace) (defpackage example (:use cl ido) (:export example-main example-var eq-hello hello)) (defvar my-var 100 "A hidden variable.") (defvar example-var nil "A public variable.") (defun my-func () "A private function." my-var) (defun example-main () "An exported function. Notice we can access all the private variables and functions from here." (interactive) (list (list (my-func) my-var) example-var (ido-completing-read "New value: " (list "foo" "bar")))) (defun eq-hello (sym) (eq sym 'hello)) (end-package)
end-package at the end, which is not needed in Common
Lisp. That’s part of what makes it crude.
If you run those functions and try changing the assignment of
non-exported symbols, you’ll see the namespace separation in
my-func are a completely different symbols than
the ones you’re seeing after
It’s really simple in how it works (it’s 40 lines of code). The
defpackage macro takes a snapshot of the symbol table. Then new
symbols get interned through various function and variable
end-package compares the current symbol table
to the snapshot and uninterns any new symbols. These symbols will be
unaccessible to other code, effectively giving them their own
Snapshots are pushed onto a stack, so it’s safe to create a new
package within another package, as long as
end-package is used
properly. This is necessary when one namespaced package depends on
another, because the dependency will tend to be loaded in the middle
of defining the current package.
in-package is not provided, so there’s no way to get the symbols
back to where they can be accessed. It’s impossible to modify a
package using fake namespacing. Worst of all, implementing
in-package is currently (and will likely always be) impossible. When
symbols are uninterned they would need to be stored in a package
symbol table for future re-interning.
in-package’s job would be to
unintern and store away the current package’s symbols and then place
the new package’s symbols into the main symbol table.
However, symbols cannot be re-interned. This is because it’s impossible for a symbol to exist in two different obarrays at the same time, so the functionality is intentionally not provided. An obarray is an Elisp vector containing symbols. It’s treated like a hash table: the symbol is hashed to choose a location in the vector. If the slot is already taken, the symbol is invisibly chain behind the residing symbol by an inaccessible linked list. If the symbol was in two obarrays at once, it would need to be able to chain to two different symbols at the same time.
Providing access to symbols through a colon-specificed namespace
my-package:my-symbol) is also currently impossible — without
hacking in C anyway.
There’s a neat trick to the
:export list. The
definition actually ignores that list altogether, because it works
automatically. By the time
defpackage is invoked, the listed symbols
have already been interned by the reader, so they get stored in the
I doubt I’ll ever make use of this for my own packages. This was mostly a fun exercise in toying with Elisp.