4

Over the weekend, I had a name clash that was very hard to track down, but I managed to boil it down to a short example - thing is, I thought the package system was supposed to protect me from this, so I'm wondering how it can in future.

If I do this:

(ql:quickload "cl-irc")

(defpackage #:clash-demo
  (:use #:cl
        #:cl-irc))

(in-package #:clash-demo)

;; This is the name that clashes - I get a warning about this if I compile
;; this interactively (i.e. from slime) but not if I quickload the whole project.
(defun server-name (server)
  (format nil "server-name ~a" server))

;; This needs an IRC server to work - if you have docker
;; then this will do the trick:
;;
;;     docker run -d --rm --name ircd -p 6667:6667 inspircd/inspircd-docker
(defparameter *connection*
  (cl-irc:connect :nickname            "clash-demo"
                  :server              "localhost"
                  :port                6667
                  :connection-security :none
                  :username            "username"))

After the above, I get the following warning when defining server-name:

WARNING: redefining CL-IRC:SERVER-NAME in DEFUN

And the following error if I try and print *connection* (in my more full-fledged project I got a missing slot in a class that I'd defined - I think the root cause of both the problem I found and the minimal example above is the same though):

Control stack guard page temporarily disabled: proceed with caution

While I get the warning if I define things interactively, in practice I moved a bunch of code into a quickproject:make-project'd and ql:quickload-ed it, which I think silenced the warning as it always loaded cleanly, hence why it took me so long to track down the name clash.

My questions are:

  1. Isn't the package system supposed to protect me from this? I think I can sort of see why the above happens - the reader's already seen the symbol server-name so it thinks I'm referring to the already defined cl-irc:server-name, and re-uses that - but surely the package system should somehow allow me to work around this?

  2. I'm assuming the warning when I quickload-ed the project was silence because quickload assumes I don't want to see warnings from projects, is there a way I can make this more forceful when I load projects I'm making so that it raises an error, or at least warns me of these name clashes? For all I know there are a bunch more that just haven't caused me a problem yet.

I was expecting either (i) the names not to clash (i.e. my file would define the symbol clash-demo:server-name, not re-use cl-irc:server-name and cause it to be redefined) or (ii) this to be an error, or at least a warning when I quickload the project.

Thanks very much in advance for any advice!

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • Your package is *using* the CL-IRC package. Means that all exported CL-IRC symbols are accessible in your package. CL-IRC has a SERVER-NAME symbol with a function binding. There is no longer a local symbol SERVER-NAME, since you are *using* the SERVER-NAME from the other package. Now you are redefining this function. That's normal that's the normal consequence of using all the exported symbols of that package. ----- If you want to have your private SERVER-NAME function you should not import such a symbol into your package. – Rainer Joswig Dec 19 '22 at 11:11
  • Thanks for the response - I mean I can see why that was happening, but again is there a way to protect myself against inadvertently doing this again? It's useful to `:use` packages and I see others doing it, it seems like the general convention, or is there some alternate way you'd recommend doing this? – John Graham Dec 19 '22 at 11:23
  • 1
    Use `(ql:quickload ... :verbose t)` to get the usual warning behavior. – Xach Dec 21 '22 at 19:58

2 Answers2

2

Briefly, no: the package system is not meant to protect you against this. If you use a package CL-IRC then you're saying that you want, for instance, to get the symbol CL-IRC:SERVER-NAME when you type server-name. What you are not saying is whether you should be allowed to modify the values associated with that symbol in any way, which is an orthogonal question. The package system is just about names, not values.

In the case of functions, then it's very often (but not always! consider loading patches) a mistake to define a function with a given name in multiple contexts. In the case of variables that's slightly less clear: it's probably a mistake if there are multiple def* forms for a given variable in different contexts, but simply assigning to the variable might well be fine.

So what is needed is a way for defining macros (defun etc) to be able to detect this and complain about it.

CL does not provide such a mechanism. Many implementations do however, either by detecting 'different contexts' (which I have been vague about) or by providing a way of saying that certain packages are sacred and redefinitions should not be allowed, or both.

In this case, the implementation has warned you about the redefinition, but Quicklisp may have suppressed that. I am not sure how to desupress warnings like this in Quicklisp.

In summary the answer is that the problem of controlling and limiting redefinition is orthogonal to what the package system does, and unfortunately CL does not provide a standard solution to this second problem.


If you are interested I have a little shim which uses the condition system to make very sure warnings are treated as errors in contexts like this. I could append it to this answer, but not until after Christmas probably.

ignis volens
  • 7,040
  • 2
  • 12
2

Suppose you really like packages A and B and want to :use both of them in your package MINE. They have a hundred external symbols each and you don't want to have to type any package prefixes. However, they both export a symbol with the same name, a:frob and b:frob. If you simply :use them, you will get a symbol conflict.

To resolve the conflict, there are three options to decide what to do when you're in package MINE and you refer to the unqualified symbol frob:

  • Prefer A so it refers to the the symbol a:frob: (defpackage mine (:use a b) (:shadowing-import-from a frob))
  • Prefer B so it refers to the symbol b:frob: (defpackage mine (:use a b) (:shadowing-import-from b frob))
  • Prefer neither so it refers to mine::frob: (defpackage mine (:use a b) (:shadow frob)) - then, to use one from A or B you must write a:frob or b:frob explicitly

Any of these three cases may be preferable depending on your situation. Common Lisp will not automatically choose one for you. I think this is a reasonable design choice.

Xach
  • 11,774
  • 37
  • 38