字节编译函数使用一种特殊的数据类型:闭包。 闭包既用于字节编译的 Lisp 函数,也用于解释执行的 Lisp 函数。 当此类对象作为待调用的函数出现时,Emacs 会使用相应的解释器 来执行字节码或未编译的 Lisp 代码。
在内部实现上,闭包与向量非常相似;
可以使用 aref 访问其元素。
它的打印表示形式也与向量类似,但在左方括号 ‘[’ 前多了一个 ‘#’。
闭包至少包含三个元素,无最大数量限制,但仅有前六个元素有常规用途:
参数描述符。它可以是参数列表(详见 参数列表的特性),
也可以是对所需参数数量进行编码的整数。
若为整数形式:
第 0~6 位表示参数的最小数量;
第 8~14 位表示参数的最大数量;
若参数列表使用 &rest,则第 7 位设为 1;否则为 0。
当闭包是字节码函数时: 若 argdesc 是列表,执行字节码前会对参数进行动态绑定; 若 argdesc 是整数,执行代码前会将参数压入字节码解释器的栈中。
对于解释执行的函数,该元素是构成函数体的(非空)Lisp 形式列表; 对于字节编译的函数,该元素是包含字节码指令的字符串。
对于字节编译的函数,该元素存储字节码引用的 Lisp 对象向量,
包括用作函数名和变量名的符号。
对于解释执行的函数:
若函数使用旧版 Emacs Lisp 的动态作用域方言,该元素为 nil;
否则,该元素存储函数的词法环境。
该函数所需的最大栈空间。此元素对解释执行的函数无实际作用。
文档字符串(若有);否则为 nil。
若文档字符串存储在文件中,该值也可能是数字或列表。
可使用 documentation 函数获取真实的文档字符串(see Access to Documentation Strings)。
交互规范(若有)。可以是字符串或 Lisp 表达式;
非交互式函数的该元素值为 nil。
以下是字节码函数对象的打印表示示例,对应命令 backward-sexp 的定义:
#[256 "\211\204^G^@\300\262^A\301^A[!\207" [1 forward-sexp] 3 1793299 "^p"]
创建字节码对象的原生方法是使用 make-byte-code:
该函数构造并返回一个闭包,作为以 elements 为元素的字节码函数对象。
请勿自行构造字节码函数的元素——若元素内容不一致, 调用该函数时可能导致 Emacs 崩溃。 应始终交由字节编译器创建这些对象(编译器会保证元素的一致性)。
创建解释执行函数的原生方法是使用 make-interpreted-closure:
该函数构造并返回表示解释执行函数的闭包:
args:函数的参数列表;
body:函数体(必须是非 nil 的 Lisp 形式列表);
env:词法环境(格式与 eval 所用一致,see 求值);
docstring(可选):文档字符串(非 nil 时应为字符串);
iform(可选):交互形式(非 nil 时应为 (interactive arg-descriptor) 格式,see Using interactive)。