Common Lisp - Finding all functions in a package

Series: common-lisp August 30, 2015

On the way to resume the development of Ulquikit, I needed a way to retrieve all functions from a package so that a Ulquikit command could be automatically detected and invoked when necessary. Common Lisp doesn’t provide this out of the box. However, it’s very easy to build one with do-all-symbols.

do-all-symbols takes the following form: (do-all-symbols (symbol package) *body), binding symbol to all symbols in package one at a time and execute body each time. A function symbol can be checked with fboundp, which returns t if that symbol corresponds to a function. We then have the first working version below:

(defun all-function-symbols (package)
  "Retrieves all functions in a package."
  (let ((res (list)))
    (do-all-symbols (sym package)
      (when (fboundp sym)
        (push sym res)))
    res))

Let’s try it out:

(defpackage #:foobar
  (:use :cl)
  (:export #:public-func))

(in-package #:foobar)
(defun private-func () (format t "A private function~%"))
(defun public-func () (format t "A public function~%"))

(in-package :cl-user)
(format t "~{~A~%~}" (all-function-symbols :foobar))

This simple implementation introduces two problems: 1) it returns imported functions from other packages that the current package uses, and 2) it doesn’t check if package is a package designator. Both are easily tackled.

The second problem could be solved by checking if the return value of (find-package package) is not nil. The first problem is then consequently solved by capturing the return value of find-package and check if it’s eql-ed to the corresponding package of sym using (symbol-package sym). In short, the condition looks like:

(when (and (fboundp sym)
           (eql (symbol-package sym)
                (find-package package)))
  ...)

Putting everything together:

(defun all-function-symbols (package)
  "Retrieves all functions in a package."
  (when (find-package package)
    (let ((res (list)))
      (do-all-symbols (sym package)
        (when (and (fboundp sym)
                   (eql (symbol-package sym)
                        (find-package package)))
          (push sym res)))
      res)))

Afer refactoring, some optimization and error signaling, we have the final version:

(defun all-function-symbols (package-name)
  "Retrieves all function symbols from a package."
  (declare ((or package string symbol) package-name))
  (the list
       (let ((lst (list))
             (package (find-package package-name)))
         (cond (package
                (do-all-symbols (symb package)
                  (when (and (fboundp symb)
                             (eql (symbol-package symb) package))
                    (push symb lst)))
                lst)
               (t
                (error "~S does not designate a package" package-name))))))