包(Package)

Table of Contents

1. symbol 的有效范围

Common Lisp 用 package 来限定 symbol 的有效范围,全局变量 *package* 记录了当前所在包(叫作:home-package):

*package*                           ; => #<PACKAGE "COMMON-LISP-USER">

Common Lisp 启动后默认在 common-lisp-user 包里。

当 REPL 捕获到一个 symbol 时,首先在当前包中查找 symbol 是否存在,否则就创建一个。通过 find-symbol 可以查找 symbol。

相关术语:

  • external:在定义 package 时,指定 export 的符号
  • internal:未导出的符号
  • inherited:defpackage 时,被 :use 的包称为继承包(使用 :use 时,只有外部包可以被导入)
  • accessible:1、可访问,只能通过 find-symbol 找到的符号;2、非限定名指向的符号(就是不通过“:”引用的符号)
  • present:在包里能找到符号,就是 present

2. 自带的三个标准包

  • common-lisp-user,别名是 cl-user。
  • common-lisp,别名是 cl。
  • keyword,任何包都可以访问 keyword 包里的符号。

3. defpackage

defpackage 用于定义一个新的包,例:

(defpackage :cl-tld
  (:use :cl)
  (:export :get-tld :get-domain-suffix :test-cl-tld))

参数说明:

  • :import-from,指定符号是从哪个包中导出:
(defpackage #:test1 (:import-from :common-lisp :+)) ; #:test1 这个包里,除了“+”,都必须加包名引用
  • :shadow:不继承指定的符号
  • :shadowing-import-from:指定符号从哪个包继承

4. use-package

把 accessible 的符号导到当前包里。

5. make-package

一般来说,建议用 defpackage。make-package 和 defpackage 区别如下:

  • make-package 可选参数没 defpackage 多,比如没有导出符号的参数,得这样写:
(eval-when (load compile eval)
  (unless (find-package :just-test)
    (make-package :just-test
                  :use '(cl))))

(make-package :just-test :use '(cl))

(in-package :just-test)

(export 'heihei) ;; 得手动 export 符号

(defun heihei ()
  "heihei")
  • make-package 返回包名,可以绑定到符号上:
(defvar just-test (make-package :just-test))
  • 需要手动 eval-when,如果写成:
(make-package just-test :use '(cl))

在编译时会报错:

(compile-file "x.lisp") ; => Package JUST-TEST does not exist.

6. 销毁包

使用 unuse-package 可销毁 use-package 导入的所有符号。

(use-package :hi)                    ; => T

;;; say-hi 是 hi 包里 export 的函数
(say-hi)                       ; => "hi"
(unuse-package :hi)            ; => T
(say-hi)                       ; => undefined function: SAY-HI

delete-package 可以销毁加载的包。

7. 与 package 有关的函数

list-all-packages:列出当前加载的所有 package。

package-used-by-list:列出指定的包被哪些包 use 了。