EmacsLisp表达式求值全解析
立即解锁
发布时间: 2025-10-26 00:26:07 阅读量: 4 订阅数: 24 AIGC 

### Emacs Lisp 表达式求值全解析
#### 1. 表达式求值基础
在 Emacs Lisp 里,表达式求值由 Lisp 解释器完成。它接收一个 Lisp 对象作为输入,然后计算该对象作为表达式的值。具体的求值方式取决于对象的数据类型。解释器会自动对程序的部分内容进行求值,也能通过 Lisp 原始函数 `eval` 显式调用。
打算进行求值的 Lisp 对象被称作形式(form)或表达式。形式是数据对象,并非单纯的文本,这是类 Lisp 语言和典型编程语言的根本区别之一。任何对象都能求值,但实际中,经常求值的对象主要是数字、符号、列表和字符串。
读取 Lisp 形式后再对其求值是常见操作,但读取和求值是相互独立的活动,二者可单独进行。读取本身不会求值,它只是把 Lisp 对象的打印表示转换为对象本身。是否将读取的对象作为形式求值,由调用 `read` 的代码决定。
求值是个递归过程,对一个形式求值通常会涉及对其内部部分的求值。例如,对函数调用形式 `(car x)` 求值时,Emacs 会先对参数 `x` 求值,然后执行函数 `car`。若函数用 Lisp 编写,执行时会对函数体进行求值。
求值在被称为环境的上下文中进行,环境包含所有 Lisp 变量的当前值和绑定。当一个形式引用变量且未创建新绑定时,该变量会被求值为当前环境赋予的值。对形式求值还可能通过绑定变量临时改变环境。另外,求值形式可能会产生持久的改变,这些改变被称为副作用,比如 `(setq foo 1)` 就会产生副作用。
需要注意,不要把求值和命令键解释混淆。编辑器命令循环会利用活动键映射将键盘输入转换为命令(可交互调用的函数),然后使用 `call-interactively` 执行该命令。若命令用 Lisp 编写,执行命令通常会涉及求值,但这一步不被视为命令键解释的一部分。
#### 2. 形式的种类
Emacs 有三种不同类型的形式,它们的求值方式各异,分别是符号、列表和“其他所有类型”。下面逐一介绍。
- **自求值形式**:不是列表或符号的形式就是自求值形式。自求值形式求值的结果就是其本身,例如数字 `25` 求值后还是 `25`,字符串 `"foo"` 求值后仍是 `"foo"`。对向量求值不会对其元素进行求值,而是直接返回内容不变的向量。
```lisp
’123
; A number, shown without evaluation.
⇒123
123
; Evaluated as usual—result is the same.
⇒123
(eval ’123)
; Evaluated ‘‘by hand”—result is the same.
⇒123
(eval (eval ’123)) ; Evaluating twice changes nothing.
⇒123
```
在 Lisp 代码中,常利用数字、字符、字符串甚至向量的自求值特性来编写代码。但对于没有读取语法的类型,这样做比较少见,不过可以通过 Lisp 程序构建包含这些类型的 Lisp 表达式。示例如下:
```lisp
;; Build an expression containing a buffer object.
(setq print-exp (list ’print (current-buffer)))
⇒(print #<buffer eval.texi>)
;; Evaluate it.
(eval print-exp)
⊣#<buffer eval.texi>
⇒#<buffer eval.texi>
```
- **符号形式**:符号求值时会被当作变量处理,若符号有对应变量的值,求值结果就是该变量的值;若符号作为变量没有值,Lisp 解释器会报错。
```lisp
(setq a 123)
⇒123
(eval ’a)
⇒123
a
⇒123
```
符号 `nil` 和 `t` 有特殊处理,`nil` 的值始终是 `nil`,`t` 的值始终是 `t`,不能将它们设置或绑定为其他值,所以这两个符号类似自求值形式。以 `:` 开头的符号也会自求值,通常其值不能改变。
- **列表形式分类**:非空列表形式根据其第一个元素可分为函数调用、宏调用或特殊形式,这三种形式的求值方式不同。列表的其余元素构成函数、宏或特殊形式的参数。求值非空列表时,首先会检查其第一个元素,该元素决定了列表的形式类型以及后续处理方式。与某些 Lisp 方言(如 Scheme)不同,这里的第一个元素不会被求值。
以下是列表形式分类的 mermaid 流程图:
```mermaid
graph TD;
A[非空列表形式] --> B{第一个元素类型};
B -->|符号| C[符号函数间接引用处理];
B -->|Lisp函数对象等| D[函数调用];
B -->|宏对象| E[宏调用];
B -->|特殊形式符号| F[特殊形式];
```
- **符号函数间接引用**:若列表的第一个元素是符号,求值时会检查该符号的函数单元,使用其内容替代原始符号。若内容仍是符号,这个过程(即符号函数间接引用)会重复进行,直到得到非符号对象。
```lisp
;; Build this function cell linkage:
;;
-------------
-----
-------
-------
;;
| #<subr car> | <-- | car |
<-- | first |
<-- | erste |
;;
-------------
-----
-------
-------
(symbol-function ’car)
⇒#<subr car>
(fset ’first ’car)
⇒car
(fset ’erste ’first)
⇒first
(erste ’(1 2 3))
; Call the function referenced by erste.
⇒1
```
内置函数 `indirect-function` 可方便地显式进行符号函数间接引用:
```lisp
[Function]
indirect-function function &optional noerror
```
该函数返回 `function` 作为函数的含义。若 `function` 是符号,会查找其函数定义并从该值开始处理;若不是符号,则直接返回 `function` 本身。若最终符号未绑定且可选参数 `noerror` 为 `nil` 或省略,该函数会报 `void-function` 错误;若 `noerror` 不为 `nil`,最终符号未绑定时返回 `nil`。若符号链中存在循环,会报 `cyclic-function-indirection` 错误。
以下是用 Lisp 定义 `indirect-function` 的代码:
```lisp
(defun indirect-function (function)
(if (symbolp function)
(indirect-function (symbol-function function))
function))
```
- **函数形式求值**:若被求值列表的第一个元素是 Lisp 函数对象、字节码对象或原始函数对象,该列表就是函数调用。对函数调用求值时,首先会从左到右对列表的其余元素求值,得到实际的参数值,然后使用这些参数调用函数,实际上是使用 `apply` 函数。若函数用 Lisp 编写,参数会用于绑定函数的参数变量,接着按顺序对函数体中的形式求值,最后一个形式的值就是函数调用的值。
- **Lisp 宏求值**:若被求值列表的第一个元素是宏对象,该列表就是宏调用。宏调用求值时,列表其余元素最初不会被求值,而是直接作为宏的参数。宏定义会计算一个替换形式,即宏的展开式,用它替代原始形式进行求值。展开式可以是任何形式,如自求值常量、符号或列表。若展开式本
0
0
复制全文


