Skip to content

LISP - Packages

As a program grows, you’ll inevitably have functions, variables, and classes that might share the same name. To prevent these names from colliding, Lisp provides a powerful namespacing mechanism called packages. A package is a symbol table that maps names (strings) to symbols.

Think of it like a directory for your code’s names. The symbol my-function in package-a is a completely different object from the symbol my-function in package-b.

The current package is stored in the global variable *package*. By default, you usually start in the COMMON-LISP-USER package.

defpackage: The Standard Way to Define a Package

Section titled “defpackage: The Standard Way to Define a Package”

The modern, declarative way to create a package is with the defpackage macro. It’s best practice to place all your defpackage forms in a separate file, often named package.lisp, which is loaded before any other code.

The defpackage macro has several important options:

  • :use: Specifies which other packages to inherit symbols from. Almost always includes :common-lisp.
  • :export: A list of symbols that define the package’s public API. These symbols can be accessed from other packages without a package prefix.
  • :import-from: Imports specific symbols from another package into the current package’s namespace.
  • :nicknames: A list of shorter, alternative names for the package.

Let’s create a small project for a simple calculator to see how packages work in practice. We’ll have two files: package.lisp and main.lisp.

;; package.lisp
(defpackage #:my-calculator
(:use #:common-lisp)
(:export #:add #:subtract))

Here, we define a package named my-calculator. It uses all the standard symbols from common-lisp and exports two symbols: add and subtract. These are the public functions we want other parts of our program (or other people) to use.

;; main.lisp
(in-package #:my-calculator)
;; This is an internal, un-exported helper function.
;; Other packages cannot easily access it.
(defun log-operation (op x y)
(format t "Performing ~a on ~a and ~a~%" op x y))
;; This is an exported function, part of our public API.
(defun add (x y)
(log-operation 'add x y)
(+ x y))
;; This is another exported function.
(defun subtract (x y)
(log-operation 'subtract x y)
(- x y))

The (in-package ...) declaration at the top switches the current package. All defun forms that follow will create symbols inside the my-calculator package.

Now, let’s see how to use our new my-calculator package. Imagine this is a different part of your application, or you’re using it in the REPL (Read-Eval-Print Loop).

;; Assuming the above files have been loaded.
;; To use our package, we can qualify the symbol names.
(print (my-calculator:add 10 5))
;; We can also use the :use option in another package definition,
;; or use the `use-package` function interactively.
(use-package '#:my-calculator)
;; Now the exported symbols are directly available.
(print (subtract 10 5))
;; Attempting to access an internal symbol will cause an error or warning.
;; The double-colon `::` is used to access internal symbols, but this is
;; strongly discouraged as it breaks encapsulation.
;; (my-calculator::log-operation 'test 1 2) ; <-- Don't do this!
Performing ADD on 10 and 5
15
Performing SUBTRACT on 10 and 5
5

In real-world Common Lisp projects, you don’t load files manually. You use ASDF (Another System Definition Facility) to define your project as a “system”. An .asd file tells Lisp which files belong to your system and their dependencies.

Furthermore, the de facto package manager for Common Lisp is Quicklisp. It allows you to download and manage hundreds of open-source libraries with a single command, like (ql:quickload :library-name). Mastering packages, ASDF, and Quicklisp is the foundation of modern, professional Common Lisp development.