Search code examples
packagecommon-lisp

Dynamically changing a package in Common Lisp


I find myself changing packages a lot, so I decided to create a convenience symbol macro for myself to save some time:

(in-package :cl-user)

(defpackage   :package
  (:nicknames p)
  (:export    :c)
  (:use       :cl))

(in-package :package)

(defun change-package ()
  (format t "package name > ")
  (let ((package-name (read)))
    (in-package package-name)))

(define-symbol-macro c (change-package))

I want to be able to type p:c in the REPL from within any package and change to package to the target package. The problem is that the in-package macro captures package-name and treats it as the name of the package.

I tried creating a macro of my own...

(defmacro ip (package-name)
  `(in-package ,package-name))

(defun change-package ()
  (format t "package name > ")
  (let ((package-name (read)))
    (ip package-name)))

... but of course that didn't work either, since the macro simply expands to the variable name anyway:

(setq package-name 'new-package)

(macroexpand '(ip package-name))

;; (EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
;;  (SETQ *PACKAGE* (SB-INT:FIND-UNDELETED-PACKAGE-OR-LOSE "PACKAGE-NAME")))
T

Questions:

  1. How can I pass the name of the package to the in-package macro so I can dynamically change the package?
  2. If this is not possible, what would be the fastest (least keystrokes) way to achieve it? (I am aware that slime has , + in-package option)

Solution

  • There are multiple problems:

    • IN-PACKAGE is a macro, it does not evaluate its arg.

    • you can't write a macro, because they are expanded by the compiler BEFORE runtime

    So either:

    • Eval a new IN-PACKAGE form with the desired package. Use EVAL.

    • just set cl:*package* to the desired package

    Why is IN-PACKAGE a macro? -> it expands into an EVAL-WHEN form, making sure that there is a compile time side-effect when compiling a FILE