Package-local nicknames in Common Lisp
Warning: this is a rant.
Warning: you have been warned.
Note: actually worthwhile content starts in the second subsection. You are free to skip the first one.
Since times unknown, Common Lispers have had issues with package name collisions. It was thought that nicknames would, perhaps, be a solution, but nicknames are just as global as standard names so they didn't really help much. The programmer could use
RENAME-PACKAGE, but that was just as painful since it affected the global namespace as well. This made people angry and frustrated, especially when, one day, Xach couldn't load all of Quicklisp into one Lisp image because of petty name conflicts, among other things. Perhaps this will be the solution.
To describe the problem in more detail:
cl-openglwants the nickname
CLX/GLXwants the nickname
GL:. A hypothetical Parenscript/WebGL wants the nickname
GL:. It is reasonable to want to load more than one of these libraries at once, and to want to use the nickname
GL:for more than one of them from different code, without having to remember to
RENAME-PACKAGEa bunch of packages before loading new code (particularly when doing interactive development on more than one of them at once). Specifying nicknames within the creating package leads to conflicts like these, particularly since there may be multiple 'obvious' nicknames to define. This occurs even if nobody ends up using more than one of them for a given package.
So, if package nicknames were global, then someone thought that it would be beneficial to have some sorta nicknames that weren't global. So, they were local. Local to what? Perhaps to some unit of Lisp code that could depend on other packages on its own. If we only had such a structure at our disposal...
Oh! A package was such a structure!
So, the idea of package-local nicknames was born. I have no idea when, but, around 2013 (or so), a certain someone called Nikodemus implemented that idea in SBCL. From there, PLNs trickled into other implementations.
Right now, at the end of 2019, it seems that SBCL, CCL, ECL, Clasp, ABCL, and ACL have it implemented; LispWorks is preparing a release with the implementation; and CLISP is on my personal target in order to sate my OCD and to have PLNs available across all of za contemporary Lisp warudo(1).
What does it mean? In my package, a package named
FOOBARBAZ-QUUX.FOO can be referred to as
F, and I can make use of a symbol that I call
F:FROBNICATE. In another person's package, this package could be referred to as
FOO, and the same symbol is called
FOO:FROBNICATE. Yet another person can refer to that package as
UTILS and therefore refer to that symbol as
UTILS:FROBNICATE. All of these worlds do not collide with each other, which means that package authors no longer need to try to cater to the users' possible preferences while - at the same time - biting each other's conses to try and hog the best nicknames for themselves. Nope. Nicknames that collide with each other can be long gone now: package-local nicknames do not have this shortcoming of being global.
Because, come on. Maybe I want to call your package Fred. And I want this nickname to stay between me and your package. I'mma call it that only when we're alone. I don't feel comfortable with exclaiming, "hey, everyone, please call this package Fred when you refer to it!". Only me. When I refer to it from inside my code. There are no other Freds in my
life codebase. Or, uhhh, at least in this package of mine.
And no one else should need to ever care about that.
Actual worthwhile content starts here
Package-local nicknames work just like standard nicknames, except they do not work globally. The system takes the current value of the
*PACKAGE* dynamic variable while performing package lookup; if the current package has any local nicknames, then they are taken into account when performing the package search, and the local nickname of a package resolves to the package that the local nickname is bound to.
This means, among other things, that:
FIND-PACKAGEwill use package-local nicknames when searching for package objects,
INTERNwill use package-local nicknames to find package to intern into,
- the Lisp reader will use package-local nicknames when reading Lisp symbols.
A practical example is the following:
(ql:quickload :alexandria) (defpackage my-package (:use #:cl) (:local-nicknames (#:a #:alexandria.dev.0))) ;; (2) (in-package :my-package) (defun assoc-value-or-die (alist key &key (test 'eql)) (multiple-value-bind (value foundp) (a:assoc-value alist key :test test) (if foundp value (error "Value ~S not found in alist ~S under test ~S." key alist test))))
Note the use of
(a:assoc-value alist key :test test). This will also work if we use
RENAME-PACKAGE to add a global nickname
A to package
(in-package #:cl-user) (find-package :a)
The above will likely return
NIL, since we are no longer in package
MY-PACKAGE, where the local nickname was in effect.
What does it mean? It means that we can define another package, that nicknames another package as
(ql:quickload :golden-utils) (defpackage my-other-package (:use #:cl) (:local-nicknames (#:a #:golden-utils))) ;; (3) (in-package :my-other-package) (defun assoc-value-or-die (alist key &key (test 'eql)) (multiple-value-bind (value foundp) (a:alist-get alist key :test test) (if foundp value (error "Value ~S not found in alist ~S under test ~S." key alist test))))
Again, note the use of
(a:alist-get alist key :test test).
Alexandria does not have any symbol with name
ALIST-GET, and the Golden Utils do not have any symbol named
ASSOC-VALUE. But, even if they had, there is no collision here - package-local nicknames ensure that the nickname
A resolves to completely different package objects while we stay in
That's all that you usually need to know, really. The rest is just some API for programmatic manipulation of PLNs:
package-local-nicknamesreturns the list of local nicknames of a package,
package-locally-nicknamed-by-listreturns the list of all packages that locally nickname a package,
remove-package-local-nicknameexist because the API for adding and removing global nicknames (yes, I mean you,
portability library if you want to use these across implementations. (You likely won't need one, though, since the most important part is already included in
defpackage and therefore available out of the box, sooooo~)
So, that's it! Go, write Lisp, enjoy the soon-to-be-fully-portable language extension, and may your packages never collide again in the code that you write.(4)
- TIME IS ALWAYS STOPPED IN THE LISP WORLD
ALEXANDRIAis not the proper name of the Alexandria package! It is merely a nickname.
- It makes sense, because "A" is for "aurum", latin for "gold". Trust me.
- Yes, this is actually a good reason for pjb to legitimize the fully qualified package names he'd been using for, what, maybe decades now. His package name of
COM.INFORMATIMAGO.COMMON-LISP.HEAP.MEMORYis pretty much not guaranteed to collide with anything, at least when compared to
GOLDEN-UTILS, and now it is finally possible to utilize that package (and all of pjb's software!) in a sane way without
USEing it(5) -
(:local-nicknames (#:memory #:com.informatimago.common-lisp.heap.memory))and tah-daah! It's working. God damn, pjb was right, he was right the whole damn time - it was just the Lisp world that wasn't ready for that truth.
:DEFPACKAGEis, as of now, deprecated. Seriously.
(declaim (deprecated :use)). Don't use
USE. Only use
USEif you want to use the CL package, or some equivalent of it for when you work with Qtools which has its own
USEis a bad idea in contemporary code except for internal packages that you fully control, where it is a decent idea until you forget that you mutate the symbol of some other package while making that brand new shiny
USEis the reason why Alexandria cannot nowadays even add a new symbol to itself, because it might cause name collisions with other packages that already have a symbol with the same name from some external source. It's good when it is
USEd from another package, but if Alexandria wanted to export a function named
FOO, and, in your package that
USEs Alexandria you have a
DEFUN FOO, I bet $5 that you imagine the havoc that this might break all over your Lisp image and all the code that has decided to jump ships and depend on
ALEXANDRIA:FOOthat you just hopelessly mutated while ignoring the ASDF warnings that came from compiling the file because Quicklisp doesn't show warnings by default while loading shit and you, just doin' business as usual, used
ASDF:LOAD-SYSTEMand didn't even tell Quicklisp to be
:VERBOSEwhile loading. Siiiiiiiigh...
Buuuut, that's a rant for another day.