Why do we specifiy packages using pound colon in Common Lisp?
Asked Answered
M

2

6

New Common Lisper here. I have seen packages being declared with the pound colon syntax, as in (defpackage #:foo), but I have also seen them defined as plain keywords, as (defpackage :foo).

Which is considered "better"? What is the difference between these two? I read somewhere that pound colon simply means the keyword isn't interned. Is that true? If so, what are the advantages to defining a package with an uninterned keyword?

Mojgan answered 7/11, 2022 at 14:37 Comment(0)
S
2

Package names are strings. But it is a good idea to specify them as symbols, because this buys you immunity to variants of CL which which do not have the same case behaviour as CL. As an example, Allegro CL has (or used to have: I have not looked at it for at least a decade) a mode where everything was lower-case by default. In that mode (symbol-name 'foo) would be "foo", and all the standard CL symbols were lower-case versions of themselves (so (symbol-name 'symbol-name) was "symbol-name".

If you wanted to write code which had any chance of running in an environment like that, you couldn't give packages names which were strings:

(defpackage "FOO"
  ...)

would mean that, in future, you'd need to type FOO:x and so on, which was horrible. Even worse, if you said

(defpackage "FOO"
  ...
  (:export "X"))

You'd now have to type FOO:X. And something like this:

(defpackage "FOO"
  (:use "CL")
  (:export "X"))

would fail dismally because there was no package whose name was "CL" at all: its name, of course, was "cl".

So if you needed things to work with that environment you really wanted to type package names – and symbol names in packages – as symbols.

This also would mean that your code would have a chance of running in some future CL variant which really was lower-case, which many people assumed would probably happen at some point, since case-sensitive lower-case-preferred languages had clearly won by the late 1980s.

Given that there's a question of where you want the names to be interned. For symbols in a package it is generally very undesirable to intern them in the current package:

(defpackage foo
  (:export x y z))
(use-package 'foo)

will fail.

So then there are two obvious choices:

  • intern things in the keyword package;
  • don't intern them.

It does not make much difference which you do in practice. Personally I intern package names in the keyword package as I want completion to work on them, but do not intern symbol names, because that seems just to be gratuitous clutter. So my package definitions look like

(defpackage :foo
  (:use :cl ...)
  (:export #:x #:y ...))
Suberin answered 8/11, 2022 at 12:45 Comment(2)
That's interesting, as for Allegro this is covered here in details: franz.com/support/documentation/10.1/doc/case.htmPhalanger
I like the history in this answer, thank you.Mojgan
P
6

Package names are strings, but you can designate them using symbols, so you have basically the following options:

1. (defpackage foo ...)
2. (defpackage "FOO" ...)
3. (defpackage :foo ...)
4. (defpackage #:foo ...)

It is true that #:foo is an uninterned symbol, and the reason some people prefer to use that syntax is precisely because it does not "pollute" the current package (1) or the keyword package (3). Usually (1) is a big No because your defpackage form has a side-effect on whatever package is current, which is bad. At least (3) is more deterministic, you always pollute the keyword package.

Note that "polluting" is not so much a problem of having too many symbols in a package, but also the fact that it is harder to reverse the operation: delete-package cleans the package designated by the symbol, but there is still a symbol interned in another package that probably was not there before defpackage (and if it already was there, you don't want to unintern it).

Some people don't mind and use keyword symbols everywhere, and it is not "bad", it just has a potential for being a source of problems in some corner cases. It is usually better to use either a string (2) or an uninterned symbol (4).

The advantage of strings over symbols (interned or not) is that you control the case: it is possible to have different packages with names that are only distinct in case (e.g. "test" vs "TEST"), and while the standard defines how symbols are read by default (upcased), you don't know in advance if your package definition will be read by a Lisp environment that is setup in a standard way or a custom way (e.g. inverting the case before interning).

So ideally it should be more robust to use always literal strings: they do not intern a symbol and the case is explicit.

However, for some reason people tend to prefer writing #:foo: probably because it is not written in uppercase, or because there is no need for quotes, etc. I am not sure why honestly (strings are often a code smell in other circumstances so maybe we tend to avoid them).

Anyway, it has become a bit customary to use uninterned symbols. This is usually not a problem because (i) people tend to use the standard readtable and (ii) in case they customize it, it is quite stable during the execution of a Lisp environment: whether you upcase or downcase the symbol in the defpackage of some package P, the symbol will be read the same way in the (:use P) clause of another defpackage.

Phalanger answered 7/11, 2022 at 15:45 Comment(2)
I'm wondering if the same holds for defsystem statements.Vituperation
@Vituperation I think so, it has the same behaviorPhalanger
S
2

Package names are strings. But it is a good idea to specify them as symbols, because this buys you immunity to variants of CL which which do not have the same case behaviour as CL. As an example, Allegro CL has (or used to have: I have not looked at it for at least a decade) a mode where everything was lower-case by default. In that mode (symbol-name 'foo) would be "foo", and all the standard CL symbols were lower-case versions of themselves (so (symbol-name 'symbol-name) was "symbol-name".

If you wanted to write code which had any chance of running in an environment like that, you couldn't give packages names which were strings:

(defpackage "FOO"
  ...)

would mean that, in future, you'd need to type FOO:x and so on, which was horrible. Even worse, if you said

(defpackage "FOO"
  ...
  (:export "X"))

You'd now have to type FOO:X. And something like this:

(defpackage "FOO"
  (:use "CL")
  (:export "X"))

would fail dismally because there was no package whose name was "CL" at all: its name, of course, was "cl".

So if you needed things to work with that environment you really wanted to type package names – and symbol names in packages – as symbols.

This also would mean that your code would have a chance of running in some future CL variant which really was lower-case, which many people assumed would probably happen at some point, since case-sensitive lower-case-preferred languages had clearly won by the late 1980s.

Given that there's a question of where you want the names to be interned. For symbols in a package it is generally very undesirable to intern them in the current package:

(defpackage foo
  (:export x y z))
(use-package 'foo)

will fail.

So then there are two obvious choices:

  • intern things in the keyword package;
  • don't intern them.

It does not make much difference which you do in practice. Personally I intern package names in the keyword package as I want completion to work on them, but do not intern symbol names, because that seems just to be gratuitous clutter. So my package definitions look like

(defpackage :foo
  (:use :cl ...)
  (:export #:x #:y ...))
Suberin answered 8/11, 2022 at 12:45 Comment(2)
That's interesting, as for Allegro this is covered here in details: franz.com/support/documentation/10.1/doc/case.htmPhalanger
I like the history in this answer, thank you.Mojgan

© 2022 - 2024 — McMap. All rights reserved.