关于 SBCL 的 require

对于如何加载 module,Common Lisp 标准里没有明确说明,而这几年普遍使用 ASDF 来解决依赖问题,很多 Common Lisp 实现也自带了 ASDF,并把 require 给“Hook”成了 ASDF 的实现,这里我以 SBCL 为例来分析。

require 的实现在 src/code/module.lisp 里,如下:

(defun require (module-name &optional pathnames)
  #!+sb-doc
  "Loads a module, unless it already has been loaded. PATHNAMES, if supplied,
   is a designator for a list of pathnames to be loaded if the module
   needs to be. If PATHNAMES is not supplied, functions from the list
   *MODULE-PROVIDER-FUNCTIONS* are called in order with MODULE-NAME
   as an argument, until one of them returns non-NIL.  User code is
   responsible for calling PROVIDE to indicate a successful load of the
   module."
  (let ((name (string module-name)))
    (when (member name *requiring* :test #'string=)
      (require-error "~@" 'require module-name))
    (let ((saved-modules (copy-list *modules*))
          (*requiring* (cons name *requiring*)))
      (unless (member name *modules* :test #'string=)
        (cond (pathnames
               (unless (listp pathnames) (setf pathnames (list pathnames)))
               ;; ambiguity in standard: should we try all pathnames in the
               ;; list, or should we stop as soon as one of them calls PROVIDE?
               (dolist (ele pathnames t)
                 (load ele)))
              (t
               (unless (some (lambda (p) (funcall p module-name))
                             *module-provider-functions*)
                 (require-error "Don't know how to ~S ~A."
                                'require module-name)))))
      (set-difference *modules* saved-modules))))

require 参数接受一个 module 名字和一个可选的路径参数。先看看这段代码:

(let ((name (string module-name)))

将参数 module-name 转成字符串后本地绑定到 name 上,这意味着参数可以是一个 keyword 或者字符串:

(require "xx")
(require :xx)

然后先判断模块是否已加载,如果模块没加载,才会去加载:

(unless (member name *modules* :test #'string=)

如果指定了 pathnames,将调用 load 来加载文件:

(dolist (ele pathnames t)
 (load ele)))

如果没有指定路径,则调用 *module-provider-functions* 这个全局变量里的函数来加载:

(unless (some (lambda (p) (funcall p module-name))
             *module-provider-functions*)

这里我直接在 REPL 里查看 *module-provider-functions* 里有哪些函数:

CL-USER> *module-provider-functions*
(ASDF/OPERATE:MODULE-PROVIDE-ASDF SB-IMPL::MODULE-PROVIDE-CONTRIB)

因为 require 里是用 some 函数来调用 *module-provider-functions* 列表里的函数,只要有一个调用成功就 OK 了。ASDF/OPERATE:MODULE-PROVIDE-ASDF 位于列表的第一个元素,所以默认会以 ASDF 来加载一个 module。