Emacs Lisp Defstruct Namespace Convention
One of the drawbacks of Emacs Lisp is the lack of namespaces. Every
defclass establishes one or more names in the global scope. To
work around this, package authors are strongly encouraged to prefix
every global name with the name of its package. That way there should
never be a naming conflict between two different packages.
(defvar mypackage-foo-limit 10) (defvar mypackage--bar-counter 0) (defun mypackage-init () ...) (defun mypackage-compute-children (node) ...) (provide 'mypackage)
While this has solved the problem for the time being, attaching the package name to almost every identifier, including private function and variable names, is quite cumbersome. Namespaces can almost be hacked into the language by using multiple obarrays, but symbols have internal linked lists that prohibit inclusion in multiple obarrays.
By convention, private names are given a double-dash after the
namespace. If a “bar counter” is an implementation detail that may
disappear in the future, it will be called
warn users and other package authors not to rely on it.
There’s been a recent push to follow this namespace-prefix policy more
strictly, particularly with the depreciation of
cl and introduction
cl-lib. I suspect someday when namespaces are finally introduced,
packages with strictly clean namespaces with be at an advantage,
somehow automatically supported. Nic Ferrier has proposed ideas
for how to move forward on this.
How strict are we talking?
Over the last few years I’ve gotten much stricter in my own packages when it comes to namespace prefixes. You can see the progression going from javadoc-lookup (2010) where I was completely sloppy about it, to EmacSQL (2014) where every single global identifier is meticulously prefixed.
For a time I considered names such as
with-* to be
exceptions to the rule, since these names are idioms inherited from
Common Lisp. The namespace comes after the expected prefix. I’ve
changed my mind about this, which has caused me to change my usage of
Just as in Common Lisp, by default
cl-defstruct defines a
constructor starting with
make-*. This is fine in Common Lisp, where
it’s a package-private function by default, but in Emacs Lisp this
pollutes the global namespace.
(require 'cl-lib) ;; Defines make-circle, circle-x, circle-y, circle-radius, circle-p (cl-defstruct circle x y radius) (defvar unit-circle (make-circle :x 0.0 :y 0.0 :radius 1.0)) unit-circle ;; => [cl-struct-circle 0.0 0.0 1.0] (circle-radius unit-circle) ;; => 1.0
This constructor isn’t namespace clean, so package authors should
avoid defstruct’s default. If the package is named
circle then all
of the accessors are perfectly fine, though.
To fix this, I now use another, more recent Emacs Lisp idiom: name the
create. That is, for the package
circle, we desire
circle-create. To get this behavior from
cl-defstruct, use the
;; Clean! (cl-defstruct (circle (:constructor circle-create)) x y radius) (circle-create :x 0 :y 0 :radius 1) ;; => [cl-struct-circle 0 0 1] (provide 'circle)
This affords a new opportunity to craft a better constructor. Have
cl-defstruct define a private constructor, then manually write a
constructor with a nicer interface. It may also do additional work,
like enforce invariants or initialize dependent slots.
(cl-defstruct (circle (:constructor circle--create)) x y radius) (defun circle-create (x y radius) (let ((circle (circle--create :x x :y y :radius radius))) (if (< radius 0) (error "must have non-negative radius") circle))) (circle-create 0 0 1) ;; => [cl-struct-circle 0 0 1] (circle-create 0 0 -1) ;; error: "must have non-negative radius"
This is now how I always use
cl-defstruct in Emacs Lisp. It’s a tidy
convention that will probably become more common in the future.