lexical binding lost when switching package

143 Views Asked by At

All I wanted was to load an initialisation file for swank which wouldn't affect my lisp when it's started without swank...

I first tried #+swank (defun...) in my file that's loaded from ccl-init (trying this on ccl 1.10 + windows), and soon realised it's sourced before swank is loaded (obviously).

My aim is to define a simple function in :cl-user everytime I start swank. I just ended up with a swank add-hook to load my init.lisp file, and since I want to define the function in cl-user, I tried this in the init.lisp:

(let ((current-package *package*))
  (in-package :cl-user)
  (defun cd (dir)
    (swank:set-default-directory
      (parse-namestring dir)))
  (in-package current-package))

Now, I don't recall if a defun in let was allowed, but lisp doesn't complain for it, but rather tells me that cur-pck symbol doesn't exist, and it seems when we switch the package, cur-pck binding gets out of scope. I thought cur-pck is a lexical binding, and that it should be reachable from within the lexical region, being independent from a package, am I wrong?

Why do I switch packages? I'm thinking that loading this file from swank at some initialisation point will define things in some swank package, that's why I wanted to try switching to cl-user first, define the function symbol, and switch back to let swank do it's thing.

At this point I guess I need someone to tell me I'm approaching the problem from the wrong angle, and that I should better choose an easier solution.

Additionally, out of curiosity in case above is the complete wrong approach, is there a way to define a symbol in another package within a function or a closure?

3

There are 3 best solutions below

3
On BEST ANSWER

If you want to define a function in a different package that the current one you can use a qualified symbol for a name

(defun cl-user::cd (dir)
    (swank:set-default-directory
      (parse-namestring dir)))

The binding is not being "lost". To test so yourself add (princ cur-pck) before the in-package form.

If you try evaluating (in-package *package*) you will see why your code fails to switch packages. The in-package macro does not evaluate its argument. The code that would give us the code we would want to evaluate is:

(let ((cur-pck *package*))
  (in-package :cl-user)
  (defun cd (dir)
    (swank:set-default-directory
     (1+ 2)))
  (princ cur-pck)
  `(in-package ,cur-pck)) 

However, as Rainer Joswig noted in his answer the in-package has no effect on forms already read, so it wouldn't work as desired even as a macro.

A style nitpick, don't use abbreviations, write current-package.

1
On

Switching packages in a form has no direct effect on the form

Let's look at this:

(in-package "FOO")

(let ((x 10))
  (in-package "BAR")
  (setf x 20))

Which x does it set to 20? FOO::X or BAR::X?

Well, it is FOO::X. Switching packages during execution has no effect on the symbols already read. The whole let form is read at once and the *package* value is used for that. Having an IN-PACKAGE in the form itself has no effect on the form itself.

Symbol with a package prefix

If you want to use a symbol in a certain package, just write the package prefix:

cl-user:foo   ; if FOO is exported and the package exists

or

cl-user::foo  ; if foo is not exported and the package exists

for example:

(defun cl-user::cd (...) ...)

Computing with symbols

You can also compute new symbols in packages you don't know yet:

(let ((sym-name "A-NEW-SYMBOL")
      (my-package-name "SOME-EXISTING-PACKAGE"))
  (intern sym-name my-package-name))

If the package does not exist, you can create it.

You can also set the function of a computed symbol:

(setf (symbol-function (compute-a-function-symbol))
  #'(lambda ()
      'foo))
0
On

IN-PACKAGE is a macro, not a function. The problem in your code is that saying (in-package cur-pck) tries not to switch into the package denoted by the cur-pck variable but to the package named CUR-PCK (which obviously does not exist).

You can set the package temporarily with

(let ((*package* (find-package :cl-user)))
  (defun cd (dir)
    ...))

But then again, the easiest way to achieve what you are doing would be

(defun cl-user::cd (dir)
  ...)

which totally eliminates the need to set the current package.