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 code),

(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)

Notice 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 action. my-var and my-func are a completely different symbols than the ones you’re seeing after end-package.

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 definitions. Finally 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 namespace.

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 defpackage macro 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 snapshot.

I doubt I’ll ever make use of this for my own packages. This was mostly a fun exercise in toying with Elisp.

Have a comment on this article? Start a discussion in my public inbox by sending an email to ~skeeto/public-inbox@lists.sr.ht [mailing list etiquette] , or see existing discussions.

null program

Chris Wellons

wellons@nullprogram.com (PGP)
~skeeto/public-inbox@lists.sr.ht (view)