===app
涉及文件: l1-files.lisp l1-init.lisp 做者: FreeBlues 2013-08-19less
===ide
require 使用场景, 使用 quicklisp 安装好一个模块后,该模块实际上并未被自动加载到 lisp 映像中, 因此每次使用该模块以前, 须要执行 (require 模块名) 来加载该模块.函数
provide 使用场景, 自定义模块时, 须要在该模块代码最后一行执行 (provide 模块名) 来保证该模块被加载一次后就把模块名导入到 *module* 列表中.ui
require 用来加载一个模块到 lisp 映像, 若是它已经被加载过, 则保持原样, 不会从新加载(看起来跟 load 函数相似, 不过 load 须要输入文件路径和文件名, 而 require 则只要提供模块名就能够了). 能够指定加载路径, HyperSpec 中有以下几种形式:this
Examples: ;;; This illustrates a nonportable use of REQUIRE, because it ;;; depends on the implementation-dependent file-loading mechanism. (require "CALCULUS") ;;; This use of REQUIRE is nonportable because of the literal ;;; physical pathname. (require "CALCULUS" "/usr/lib/lisp/calculus") ;;; One form of portable usage involves supplying a logical pathname, ;;; with appropriate translations defined elsewhere. (require "CALCULUS" "lib:calculus") ;;; Another form of portable usage involves using a variable or ;;; table lookup function to determine the pathname, which again ;;; must be initialized elsewhere. (require "CALCULUS" *calculus-module-pathname*)
其实, 也能够这么写:atom
(require :CALCULUS)
provide 原来把一个 module 名字加入到 *module* 列表中, 若是已经存在则不加.debug
Emacs 中查看函数源代码方法: 在 REPL 中输入 (require ), 而后把光标停在 require 上, 按下 M-. 就能够打开 require 对应的源代码.rest
(defun provide (module) "Adds a new module name to *MODULES* indicating that it has been loaded. Module-name is a string designator" (pushnew (string module) *modules* :test #'string=) module) (defparameter *loading-modules* () "Internal. Prevents circularity") (defparameter *module-provider-functions* '(module-provide-search-path) "A list of functions called by REQUIRE to satisfy an unmet dependency. Each function receives a module name as a single argument; if the function knows how to load that module, it should do so, add the module's name as a string to *MODULES* (perhaps by calling PROVIDE) and return non-NIL." ) (defun module-provide-search-path (module) ;; (format *debug-io* "trying module-provide-search-path~%") (let* ((module-name (string module)) (pathname (find-module-pathnames module-name))) (when pathname (if (consp pathname) (dolist (path pathname) (load path)) (load pathname)) (provide module)))) (defun require (module &optional pathname) "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* ((str (string module)) (original-modules (copy-list *modules*))) (unless (or (member str *modules* :test #'string=) (member str *loading-modules* :test #'string=)) ;; The check of (and binding of) *LOADING-MODULES* is a ;; traditional defense against circularity. (Another ;; defense is not having circularity, of course.) The ;; effect is that if something's in the process of being ;; REQUIREd and it's REQUIREd again (transitively), ;; the inner REQUIRE is a no-op. (let ((*loading-modules* (cons str *loading-modules*))) (if pathname (dolist (path (if (atom pathname) (list pathname) pathname)) (load path)) (unless (some (lambda (p) (funcall p module)) *module-provider-functions*) (error "Module ~A was not provided by any function on ~S." module '*module-provider-functions*))))) (values module (set-difference *modules* original-modules)))) (defun find-module-pathnames (module) "Returns the file or list of files making up the module" (let ((mod-path (make-pathname :name (string-downcase module) :defaults nil)) path) (dolist (path-cand *module-search-path* nil) (let ((mod-cand (merge-pathnames mod-path path-cand))) (if (wild-pathname-p path-cand) (let* ((untyped-p (member (pathname-type mod-cand) '(nil :unspecific))) (matches (if untyped-p (or (directory (merge-pathnames mod-cand *.lisp-pathname*)) (directory (merge-pathnames mod-cand *.fasl-pathname*))) (directory mod-cand)))) (when (and matches (null (cdr matches))) (return (if untyped-p (make-pathname :type nil :defaults (car matches)) (car matches))))) (when (setq path (find-load-file (merge-pathnames mod-path path-cand))) (return path))))))) (defun wild-pathname-p (pathname &optional field-key) "Predicate for determining whether pathname contains any wildcards." (flet ((wild-p (name) (or (eq name :wild) (eq name :wild-inferiors) (and (stringp name) (%path-mem "*" name))))) (case field-key ((nil) (or (some #'wild-p (pathname-directory pathname)) (wild-p (pathname-name pathname)) (wild-p (pathname-type pathname)) (wild-p (pathname-version pathname)))) (:host nil) (:device nil) (:directory (some #'wild-p (pathname-directory pathname))) (:name (wild-p (pathname-name pathname))) (:type (wild-p (pathname-type pathname))) (:version (wild-p (pathname-version pathname))) (t (wild-pathname-p pathname (require-type field-key '(member nil :host :device :directory :name :type :version)))))))
本函数功能是把一个 module 名字加入到 *module* 中, 用来指示该 module 已经被加载, 最后返回(provide module) 中的参数 module.code
主要代码就是这条语句:
(pushnew (string module) *modules* :test #'string=)
本函数代码中一个重要的辅助函数是 pushnew, 该函数和 push 相似, 是把一个对象和一个位置的对应保存在一个相似栈的列表中, 若是该对象已经在列表中, 就不会执行, 后面这个 :test 用来选择用于比较的函数.
参考: 函数 pushnew 的代码: (defmacro pushnew (value place &rest keys &environment env) "Takes an object and a location holding a list. If the object is already in the list, does nothing; otherwise, conses the object onto the list. Returns the modified list. If there is a :TEST keyword, this is used for the comparison." (if (not (consp place)) `(setq ,place (adjoin ,value ,place ,@keys)) (let ((valvar (gensym))) (multiple-value-bind (dummies vals store-var setter getter) (get-setf-method place env) `(let* ((,valvar ,value) ,@(mapcar #'list dummies vals) (,(car store-var) (adjoin ,valvar ,getter ,@keys))) ,@dummies ,(car store-var) ,setter)))))
本函数中的重要变量 *module* 是专门为 provide 和 require 函数准备的一个空列表, 用来保存那些已经被加载到 lisp 映像中的 module 名字(大小写敏感), 它的源代码在 l1-init.lisp 中, 具体 内容以下:
(defvar *modules* nil "This is a list of module names that have been loaded into Lisp so far. The names are case sensitive strings. It is used by PROVIDE and REQUIRE.")
(defun require (module &optional pathname) …)
输入参数为 module 和 可选的路径名.
(let* ((str (string module)) (original-modules (copy-list *modules*)))
首先, 设置两个词法变量 str 和 original-modules, str 是把参数 module 转换为字符串形式, original-modules 则是把列表 *module* 的内容复制保存.
(unless (or (member str *modules* :test #'string=) (member str *loading-modules* :test #'string=))
接着, 是一个预防性判断, 要求只有当输入的参数名 module 不在 *modules* 和 *loading-modules* 两个列表中时, 才继续进行下一步, 不然说明该 module 已经被加载, 就不须要加载了.
(let ((*loading-modules* (cons str *loading-modules*)))
若是通过上述判断, module 不在 *modules* 和 *loading-modules* 两个列表中, 就把 module 加入 *loading-modules* 中, 并将其值赋予词法变量 *loading-modules* (注意, 这个 *loading-modules* 的做用范围仅仅局限于这个 let 后面的区域).
(if pathname (dolist (path (if (atom pathname) (list pathname) pathname)) (load path)) (unless (some (lambda (p) (funcall p module)) *module-provider-functions*) (error "Module ~A was not provided by any function on ~S." module '*module-provider-functions*)))))
若是输入了 pathname 参数, 那么根据这个参数去构造一个 path, 最后用 load 来加载; 若是没有输入 pathname 参数, 则利用 *module-provider-functions* 中的函数来调用 module, 若是出错则返回错误信息.
(values module (set-difference *modules* original-modules))))
最后这条语句做为整个 require 函数最后的返回值, 它使用 values 来返回多个值, 第一个值是 module 参数, 第二个值是一个列表, 比较了加载完 module 以后的 *modules* 和加载以前的 original-modules 列表的差别.
函数 set-difference 的具体表现能够看看下面这段示例:
CL-USER> (defparameter *list1* '(1 2 3 4)) *LIST1* CL-USER> *list1* (1 2 3 4) CL-USER> (defparameter *list2* '(1 2 3 4 5 6)) *LIST2* CL-USER> *list2* (1 2 3 4 5 6) CL-USER> (set-difference *list1* *list2*) NIL CL-USER> *list2* (1 2 3 4 5 6) CL-USER> *list1* (1 2 3 4) CL-USER> (set-difference *list2* *list1*) (6 5) CL-USER> *list1* (1 2 3 4) CL-USER> *list2* (1 2 3 4 5 6)
其余辅助函数, 如 module-provide-search-path, find-module-pathnames 和 wild-pathname-p 主要处理搜索路径相关的一些工做, 可自行分析.