provide 和 require 是 autoload 之外的另一种自动加载文件的方式。它们基于命名的 功能(feature) 工作。自动加载由调用特定函数触发,而功能则是在其他程序首次按名称请求时被加载。
功能名是一个符号,代表一组函数、变量等的集合。定义它们的文件应当 提供(provide) 该功能。使用这些功能的其他程序可以通过 需求(require) 该功能来确保其已被定义。如果文件尚未加载,这会加载对应的定义文件。
要需求某个功能存在,以功能名作为参数调用 require。
require 会检查全局变量 features,判断所需功能是否已经提供。
如果没有,它会从对应的文件加载该功能。
该文件应当在顶层调用 provide,将该功能加入 features;
如果没有这样做,require 会发出错误信号。
例如,在 idlwave.el 中,idlwave-complete-filename 的定义包含如下代码:
(defun idlwave-complete-filename ()
"Use the comint stuff to complete a file name."
(require 'comint)
(let* ((comint-file-name-chars "~/A-Za-z0-9+@:_.$#%={}\\-")
(comint-completion-addsuffix nil)
...)
(comint-dynamic-complete-filename)))
表达式 (require 'comint) 会在尚未加载时加载 comint.el 文件,确保 comint-dynamic-complete-filename 已被定义。
功能通常以提供它的文件命名,因此 require 不需要指定文件名。
(注意,require 语句必须放在 let 体之外,这一点很重要。
在变量被 let 绑定期间加载库可能产生意外后果,即变量在 let 退出后会变为未绑定。)
comint.el 文件包含如下顶层表达式:
(provide 'comint)
这会将 comint 加入全局的 features 列表,此后 (require 'comint) 就知道无需执行任何操作。
当 require 在文件顶层使用时,它不仅在加载时生效,在对该文件进行字节编译时也会生效(see 字节编译)。
这是为了应对被需求的包中包含字节编译器必须知晓的宏的情况。
这也可以避免字节编译器对由 require 加载的文件中定义的函数和变量发出警告。
虽然顶层的 require 调用会在字节编译期间执行,但 provide 调用不会。
因此,你可以通过先写 provide、再对同一功能写 require,确保定义文件在字节编译前已被加载,如下例所示。
(provide 'my-feature) ; Ignored by byte compiler,
; evaluated by load.
(require 'my-feature) ; Evaluated by byte compiler.
编译器忽略 provide,然后通过加载对应文件来处理 require。
加载文件时会执行 provide 调用,因此后续的 require 在文件加载时不执行任何操作。
该函数宣告 feature 已加载或正在加载到当前 Emacs 会话中。 这意味着与 feature 相关的功能已经或将要可供其他 Lisp 程序使用。
调用 provide 的直接效果是:如果 feature 不在 features 列表中,则将其添加到列表前端,并执行所有等待该功能的 eval-after-load 代码(see 加载相关钩子)。
参数 feature 必须是一个符号。
provide 返回 feature。
如果提供了 subfeatures,它应当是一个符号列表,表示此版本 feature 提供的一组特定子功能。
你可以使用 featurep 检查某个子功能是否存在。
子功能的设计意图是:当一个包(对应一个 feature)足够复杂,需要为其不同部分或功能分别命名时使用,这些部分可能加载或未加载,可能存在或不存在于某个版本中。
see Testing Availability of Network Features 提供了一个示例。
features
⇒ (bar bish)
(provide 'foo)
⇒ foo
features
⇒ (foo bar bish)
当加载文件以满足自动加载需求,且因执行文件内容出错而中断时,加载过程中产生的所有函数定义或 provide 调用都会被撤销。See 自动加载。
该函数检查 feature 是否已存在于当前 Emacs 会话中(通过 (featurep feature) 实现;详见下文)。参数 feature 必须是一个符号。
如果该功能不存在,则 require 会通过 load 加载 filename 文件。若未提供 filename,则将符号 feature 的名称作为要加载的基础文件名。
但在此情况下,require 要求文件必须带有 ‘.el’ 或 ‘.elc’ 后缀(可附加压缩后缀);仅以 feature 为名的文件不会被使用。
(变量 load-suffixes 规定了确切的 Lisp 文件后缀要求。)
如果 noerror 为非 nil 值,则会抑制文件实际加载过程中产生的错误。此时若文件加载失败,require 返回 nil。
默认情况下,require 返回 feature。
如果文件加载成功但未提供 feature,require 会针对缺失的功能发出错误信号。
该函数的行为与 require 类似,但存在一个例外情况:当 feature 已加载时(即已存在于 features 列表中;详见下文)。
若 feature 已加载,该函数会检查提供此功能的文件是否与 filename 不同;若不同,默认会发出错误信号。
如果可选参数 noerror 的值为 reload,函数不会报错,而是强制重新加载 filename;
若 noerror 为其他非 nil 值,函数会发出警告,提示 feature 已由另一个文件提供。
如果 feature 已在当前 Emacs 会话中被提供(即 feature 是 features 的成员),该函数返回 t。
若 subfeature 为非 nil 值,则仅当该子功能也被提供时(即 subfeature 是 feature 符号的 subfeature 属性的成员),函数才返回 t。
该变量的值是一个符号列表,代表当前 Emacs 会话中已加载的功能。列表中的每个符号都是通过调用 provide 加入的。
features 列表中元素的顺序无实际意义。
use-package 宏提供了一种便捷的方式来加载功能并配置其使用方式。
它既能像 require 一样需求某个功能,又能指定功能实际加载时要运行的代码(类似加载时钩子;see 加载相关钩子)。
use-package 的声明式语法使其在用户初始化文件中使用起来格外简便。
该宏指定如何加载命名为 feature 的功能,以及如何配置和自定义其使用方式。参数 args 为若干键值对。 其中重要的关键字及其取值如下:
:init forms指定在加载 feature 之前要执行的 forms(表单)。
:config forms指定在加载 feature 之后要执行的 forms(表单)。
:defer condition若 condition 为非 nil 值,表示延迟加载 feature,直到首次使用该功能的任意自动加载命令或变量时才加载。
若 condition 为数字 n,表示在空闲 n 秒后加载 feature。
:commands commands…指定要为 feature 自动加载的命令。
:bind keybindings…指定 feature 相关命令的 keybindings(按键绑定)。每个绑定的格式为:
(key-sequence . command)
or
(:map keymap (key-sequence . command))
其中 key-sequence(按键序列)的格式需符合 kbd 宏的要求(see Key Sequences)。
关于 use-package 的更多细节,参见 use-package 用户手册。