12. Packages, Libraries, and Modules

Introduction

;;;-----------------------------
(defpackage :alpha (:use :cl))
(in-package :alpha)
(setf name "first")

(defpackage :omega (:use :cl))
(in-package :omega)
(setf name "last")

(in-package :cl-user) ; default package, kinda like Perl's 'main' package
(format t "Alpha is ~A, omega is ~A.~%" alpha::name omega::name)
;; Alpha is first, omega is last.
;;;-----------------------------
(load "FileHandle.lisp")                ; run-time load
(load "FileHandle.fasl")        ; explicitly load pre-compiled version
(load "FileHandle")          ; same as previous two, will prefer .fasl
(require :FileHandle)                   ; similar, still run-time

;; Could use LOAD here instead of REQUIRE; it's the EVAL-WHEN that
;; makes this compile-time.  Note that it's fairly unusual to do this
;; explicitly with EVAL-WHEN.  Instead, one normally ensures that the
;; requisite packages have already been loaded before attempting to
;; compile a file, e.g., using a build system such as ASDF.
(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :FileHandle))                ; compile-time

;; The Cards::Poker example is no different than the above in CL.  CL
;; has no in-built hierarchicalp package system, hence no translation
;; of "::" into a directory separator, etc.
;;;-----------------------------
(defpackage :cards.poker                
  (:use :cl)                            ; equivalent to Perl line 2
  (:export :shuffle :*card-deck*))      ; line 4
(in-package :cards.poker)               ; line 1
(defparameter *card-deck* nil)          ; line 5
(defun shuffle ())                      ; line 6
;;;-----------------------------

Defining a Module's Interface

;;;-----------------------------
(defpackage :your-module (:use :cl)
            (:export ...))              ; one way to export
(in-package :your-module)

(defparameter *version* 1.00) ; not standard CL, but one could ascribe meaning by convention

(export '(...))                         ; another way to export

;; There is no built-in equivalent to EXPORT_OK.  EXPORT_TAGS also has
;; no standard CL counterpart, but something similar can easily be
;; implemented.  See IMPORT-TAGS, defined in the appendix.
(defparameter *export-tags*
  '(:TAG1 ( ... )
    :TAG2 ( ... )))

;;;;;;;;;;;;;;;;;;;;;;;;
;; your code goes here
;;;;;;;;;;;;;;;;;;;;;;;;
;;;-----------------------------
(defpackage :my-package (:use cl)
            (:use :your-module)) ; Import default symbols into my package.
(defpackage :my-package (:use cl)
            (:import-from :your-module ...)) ; Import listed symbols into my package.
;; Or at some point in my-package do this:
(in-package :my-package)
(import '(your-module:symbol1 your-module:symbol2 ...)) ; Import listed symbols into my package.
(defpackage :my-package (:use cl)
            (:import-from :your-module)) ; Do not import any symbols
;;;-----------------------------
(export '(f1 f2 my-list))
;;;-----------------------------
;; No equivalent to EXPORT_OK
;;;-----------------------------
(import '(your-module:op-func your-module:your-table your-module:f1))
;;;-----------------------------
;; From within the package you want to import into, do this
;; (IMPORT-TAGS defined in appendix, not standard CL).
(import-tags :your-module :DEFAULT)
(import 'your-module:your-table)
;;;-----------------------------
(defparameter *export-tags*
  '((:functions (f1 f2 op-func))
    (:variables (your-list your-table))))
;;;-----------------------------
(import-tags :your-module :functions)
(import 'your-module:your-table)
;;;-----------------------------

Trapping Errors in require or use

;;;-----------------------------
;; In the following example, EVAL-WHEN is like the Perl example's
;; BEGIN.
(eval-when (:compile-toplevel :load-toplevel :execute)
  (multiple-value-bind (retval why-not)
      (ignore-errors (require :mod))    ; no import
    (when why-not
      (warn "couldn't load :mod: ~A" why-not))))

;; Note that there is no "use" in CL, but you can REQUIRE and then
;; USE-PACKAGE if it was successful.
(eval-when (:compile-toplevel :load-toplevel :execute)
  (multiple-value-bind (retval why-not)
      (ignore-errors (require :mod))
    (if why-not
      (warn "couldn't load :mod: ~A" why-not)
      (use-package :mod))))             ; imports into current package
;;;-----------------------------

;; This code is written in order to be as much like the Perl example
;; as possible, and likely isn't what you'd use in practice.  Normally
;; you'd just take advantage of the existing features of, for example,
;; ASDF.
(eval-when (:compile-toplevel :load-toplevel :execute)

  (let ((dbs '(giant.eenie giant.meanie mouse.mynie moe))
        found)

    (dolist (module dbs)
      (multiple-value-bind (_ why-not)
          (ignore-errors (require module))
        (unless why-not
          (let (import-fn)
            (let ((*package* (find-package module)))
              ;; The following line assumes the module defines
              ;; MY-IMPORT (note it can't be called IMPORT b/c that
              ;; clashes with CL's built-in IMPORT).
              (setf import-fn (symbol-function (find-symbol "MY-IMPORT")))
            (funcall import-fn)))
          (setf found t)
          (return))))

    (unless found (error "None of ~{~A~^ ~} loaded" dbs))))
;;;-----------------------------

Delaying use Until Run Time

;;;-----------------------------
(eval-when (:compile-toplevel :load-toplevel :execute)
  (unless (and (= 3 (length *posix-argv*)) ; Perl skips past executable
               (= 2 (length (perl-grep *posix-argv* ; PERL-GREP defined in Appendix
                              (scan "^\\d+$" it)))))
    (error "usage: ~A num1 num2" (car *posix-argv*))))

(require :some.module)
(require :more.modules)
;;;-----------------------------
;; This would be silly since CL's numbers already are "big".
(when opt-b (require :math.bigint)) 
;;;-----------------------------
;; The following is just to make the examples work.
(defpackage :fcntl (:use cl))
(in-package :fcntl)
(defconstant +O-EXCL+ #x800)
(defconstant +O-CREAT+ #x200)
(defconstant +O-RDWR+ #x2)

(export '(+O-EXCL+ +O-CREAT+ +O-RDWR+))
(provide 'fcntl)

(defpackage :my-package
  (:use cl)
  (:import-from :fcntl +O-EXCL+ +O-CREAT+ +O-RDWR+))
(in-package :my-package)
;;;-----------------------------
(require :fcntl)
(import '(fcntl:+O-EXCL+ fcntl:+O-CREAT+ fcntl:+O-RDWR+))
;;;-----------------------------
(in-package cl-user)
(defun load-module (module)
  (require module)
  (import module))                      ; WRONG
;;;-----------------------------
(load-module :fcntl '(fcntl:+O-EXCL+ fcntl:+O-CREAT+ fcntl:+O-RDWR+))
(defun load-module (module symbols)
  (require module)
  ;; No need for die because REQUIRE will do it for us.
  (import symbols))
;;;-----------------------------
;; CL doesn't have anything like Perl's autouse, as far as I can tell.
;; The following is a rough implementation of it.  Usage:
;;   (autouse 'my-package (xyz abc))
;; Where XYZ and ABC are functions defined within MY-PACKAGE.
(defmacro autouse (pack symbols)
  (list*
   'progn
   (loop
      for symb in symbols
      collect
        `(defun ,symb (&rest args)
           (require ',pack)
           (apply (find-symbol ,(symbol-name symb) ',pack) args)))))
;;;-----------------------------

Making Variables Private to a Module

;;;-----------------------------
(defpackage :alpha (:use :cl))
(in-package :alpha)

(defparameter aa 10)
(export 'aa)
(defparameter x "azure")

(defpackage :beta (:use :cl))
(in-package :beta)

(defparameter bb 20)
(export 'bb)
(defparameter x "blue")

(in-package :cl-user)         ; closest thing to Perl's 'main' package
;; Unlike the Perl example, without the explict EXPORT and IMPORT the
;; symbols won't be visible in the CL-USER package.
(import '(alpha:aa beta:bb))
(format t "~A, ~A, ~A, ~A, ~A~%" aa bb (if (boundp 'x) x "") alpha::x beta::x)
;; 10, 20, , azure, blue
;;;-----------------------------
;; flipper.lisp
(defpackage :flipper
  (:use cl cl-ppcre)
  (:export flip-words flip-boundary))
(in-package :flipper)

(defvar *separatrix* #\Space) ; default to blank; must precede functions

(defun flip-boundary (&optional separatrix)
  (prog1 *separatrix*
    (when separatrix
      (setf *separatrix* separatrix))))

(defun flip-words (line)
  (let ((words (split *separatrix* line)))
    (format nil (format nil "~~{~~A~~^~A~~}" *separatrix*) (reverse words))))
;;;-----------------------------
    

Determining the Caller's Package

;;;-----------------------------
;; #. forces *PACKAGE* to be evaluated at compile-time, so that it
;; won't pick up the dynamic value of *PACKAGE* (i.e., the package of
;; the calling function, which may be different.)
(setf this-pack #.*package*)
;;;-----------------------------
;; As long as the following does not appear at the "top level" of the
;; file, it will contain the current package (i.e., the "outermost"
;; calling function's, assuming it hasn't been rebound explicitly.)
(setf that-pack *package*)
;;;-----------------------------
(format t "I am in package *package*~%")        ; WRONG!
;; I am in package *package*
;;;-----------------------------
(defpackage :alpha (:use cl beta))
(in-package :alpha)

;; As (I think) the Perl example does, this presupposes that TEMP is
;; set to an already-open stream (in BETA), e.g.:
;;  (setf temp (open "/usr/share/dict/words"))
(runit "(setf line (read-line temp))")

(defpackage :beta 
  (:use cl)
  (:export runit))
(in-package :beta)

(defun runit (codestr)
  ;; The following is not a good idea, but is intended to make this
  ;; example work the way the Perl one does, by causing the EVAL to
  ;; occur in the context of this package (BETA) rather than the
  ;; caller's (ALPHA's).
  (in-package #.(package-name *package*))
  ;; EVAL will throw an error if there's any problems, no need to do
  ;; it explicitly like the Perl does.
  (eval (read-from-string codestr)))
;;;-----------------------------
(defpackage :beta 
  (:use cl)
  (:export runit))
(in-package :beta)

(defun runit (codestr)
  ;; CL is essentially the reverse of Perl in this regard, the default
  ;; behavior already works the right way without the need to use
  ;; something like Perl's 'caller'.
  (eval (read-from-string codestr)))
;;;-----------------------------
(defpackage :alpha (:use cl beta))
(in-package :alpha)

(runit (lambda () (setf line (read-line temp))))

(defpackage :beta
  (:use cl)
  (:export runit))
(in-package :beta)

(defun runit (coderef)
  (funcall coderef))
;;;-----------------------------
(in-package cl-user)

(defparameter *fh* (open "/etc/services")) ; don't have /etc/termcap on my machine
(multiple-value-setq (a b c) (values-list (nreadline 3 "*fh*")))

(defun nreadline (count handle)
  (unless (plusp count) (error "COUNT must be > 0"))
  (let ((handle (symbol-value (find-symbol (string-upcase handle)))))
    (unless (open-stream-p handle)
      (error "need open filehandle"))
    (loop
       repeat count
       collect (read-line handle))))
;;;-----------------------------

Automating Module Clean-Up

Keeping Your Own Module Directory

;;;-----------------------------
;; In section 12.7 we assume the use of ASDF, which is the de facto
;; standard package mechanism.
(loop 
   for path in asdf:*central-registry*
   for i from 0
   do (format t "~D ~A~%" i path))
;;0 /Users/mongo/moogle-code/cl-fnord/
;;1 /Users/mongo/common-lisp.net/cl-zztop/trunk/
;;2 (MERGE-PATHNAMES .sbcl/systems/ (USER-HOMEDIR-PATHNAME))
;;3 (LET ((HOME (POSIX-GETENV SBCL_HOME)))
;;    (WHEN HOME (MERGE-PATHNAMES site-systems/ (TRUENAME HOME))))
;;4 *DEFAULT-PATHNAME-DEFAULTS*

;;;-----------------------------
(pushnew "/projects/spectre/lib" asdf:*central-registry*)
;;;-----------------------------
(require :find-bin)
(pushnew find-bin::*bin* asdf:*central-registry*)
;;;-----------------------------
(shadowing-import find-bin::*bin*)
(pushnew (concatenate 'string *bin* "/../lib"))
;;;-----------------------------

Preparing a Module for Distribution

Speeding Module Loading with SelfLoader

Speeding Up Module Loading with Autoloader

Overriding Built-In Functions

Reporting Errors and Warnings Like Built-Ins

Referring to Packages Indirectly

Using h2ph to Translate C #include Files

Using h2xs to Make a Module with C Code

Documenting Your Module with Pod

Building and Installing a CPAN Module

Example: Module Template

Program: Finding Versions and Descriptions of Installed Modules