9.3 创建与编入符号

要理解 GNU Emacs Lisp 中符号是如何创建的,你必须了解 Lisp 是如何读取它们的。Lisp 必须保证:在同一上下文里,每次读取相同字符序列时,都能得到同一个符号。如果做不到这一点,就会造成彻底的混乱。

表当 Lisp 读取器在代码中遇到一个引用符号的名称时,它会在一张名为 obarray(符号表) 的表中查找该名称,以找到程序员所指的符号。符号表是一个以名称为索引、存放符号的无序容器。

Lisp 读取器还会处理 “简写(shorthands)”。如果程序员提供了简写,即便源代码中没有写出符号的完整名称,读取器也能找到对应的符号。See 简写符号

如果找到了具有目标名称的符号,读取器就会使用该符号。如果符号表中不存在该名称的符号,读取器会创建一个新符号并将其添加到符号表中。查找或添加某个名称的符号这一过程称为 编入(intern) 该符号,经过这一过程的符号称为 已编入符号(interned symbol)

编入操作保证了每个符号表中,任意名称只对应唯一的一个符号。其他同名符号可以存在,但不会出现在同一个符号表里。因此,只要使用同一个符号表进行读取,相同的名称就总会得到相同的符号。

编入操作通常在读取器中自动完成,但有时其他程序也需要主动执行。例如,M-x 命令通过小缓冲区以字符串形式获取命令名后,会将该字符串编入,从而得到对应名称的已编入符号。再举一个例子,一个假想的电话簿程序可以将每个查询到的人名作为符号编入,即便该符号表中原本不存在,这样就可以把信息附加到这个新符号上,比如上次被查询的时间。

没有任何一个符号表包含所有符号;事实上,有些符号不属于任何符号表。它们被称为 未编入符号(uninterned symbols)。未编入符号同样拥有其他符号所具备的四个单元;但是,访问它的唯一途径是:在其他对象中找到它,或是通过某个变量的值获取它。未编入符号在生成 Lisp 代码时有时很有用,详见下文。

Common Lisp 注意: 与 Common Lisp 不同,Emacs Lisp 不支持将同一个名称在多个不同的 “包(package)” 中编入,从而创建同名但属于不同包的多个符号。Emacs Lisp 提供了一套名为 “简写(shorthands)” see 简写符号 的命名空间机制。

Function: obarray-make &optional size

该函数创建并返回一个新的符号表(obarray)。可选参数 size 可用于指定该符号表预计容纳的符号数量,但由于符号表会根据需要自动扩容,因此这个参数几乎不会带来任何实际收益。

Function: obarrayp object

如果 object 是一个符号表(obarray),该函数返回 t,否则返回 nil

下述大部分函数会接收一个名称(字符串)作为参数,有时还会接收一个符号表作为参数。若传入的名称不是字符串,或传入的符号表并非合法的符号表对象,将触发 wrong-type-argument(参数类型错误)异常。

Function: symbol-name symbol

该函数返回作为 symbol 名称的字符串。例如:

(symbol-name 'foo)
     ⇒ "foo"

警告: 切勿修改该函数返回的字符串。这样做可能会导致 Emacs 功能异常,甚至使 Emacs 崩溃。

创建一个未注册符号(uninterned symbol)在生成 Lisp 代码时十分实用,因为在你生成的代码中,将未注册符号用作变量时,不会与其他 Lisp 程序中使用的任何变量产生命名冲突。

Function: make-symbol name

该函数返回一个新分配的未注册符号,其名称为name(参数必须是字符串类型)。该符号的变量值和函数定义均为未定义(void)状态,其属性列表为 nil。在下方示例中,sym 的值与 foo 并非 eq(全等),因为前者是一个独立的未注册符号,只是名称同样为 ‘foo’ 而已。

(setq sym (make-symbol "foo"))
     ⇒ foo
(eq sym 'foo)
     ⇒ nil
Function: gensym &optional prefix

该函数通过 make-symbol 创建并返回一个符号,其名称的生成规则为:将 gensym-counter(gensym 计数器)的值追加到 prefix(前缀)之后,并将该计数器自增。这一机制能保证对该函数的任意两次调用,都不会生成同名符号。若未指定前缀,默认使用 "g"

为避免因意外注册(intern)生成代码的打印表示形式而引发问题(see 打印表示与读入语法),建议使用 gensym 而非 make-symbol

Function: intern name &optional obarray

该函数返回名称为 name 的已注册符号。若符号表 obarray 中不存在该符号,intern 会创建一个新符号,将其加入该符号表后返回。若省略 obarray 参数,则使用全局变量 obarray 的值作为默认符号表。

(setq sym (intern "foo"))
     ⇒ foo
(eq sym 'foo)
     ⇒ t

(setq sym1 (intern "foo" other-obarray))
     ⇒ foo
(eq sym1 'foo)
     ⇒ nil

Common Lisp 注: 在 Common Lisp 中,你可以将一个已存在的符号注册到符号表(obarray)中。而在 Emacs Lisp 中无法这样做,因为 intern 的参数必须是字符串,而不能是符号。

Function: intern-soft name &optional obarray

该函数返回符号表 obarray 中名称为 name 的符号;若 obarray 中不存在该名称的符号,则返回 nil。因此,你可以使用 intern-soft 来检测指定名称的符号是否已被注册(intern)。若省略 obarray 参数,则使用全局变量 obarray 的值作为默认符号表。

参数 name 也可以是一个符号;这种情况下,如果该符号已注册在指定的符号表中,函数会返回该符号(name),否则返回 nil

(intern-soft "frazzle")        ; No such symbol exists.
     ⇒ nil
(make-symbol "frazzle")        ; Create an uninterned one.
     ⇒ frazzle
(intern-soft "frazzle")        ; That one cannot be found.
     ⇒ nil
(setq sym (intern "frazzle"))  ; Create an interned one.
     ⇒ frazzle
(intern-soft "frazzle")        ; That one can be found!
     ⇒ frazzle
(eq sym 'frazzle)              ; And it is the same one.
     ⇒ t
Variable: obarray

该变量是供 internread 函数使用的标准符号表(obarray)。

Function: mapatoms function &optional obarray

该函数会对符号表 obarray 中的每一个符号调用一次 function 函数,执行完成后返回 nil。若省略 obarray 参数,则默认使用全局变量 obarray 的值(即存储普通符号的标准符号表)。

(setq count 0)
     ⇒ 0
(defun count-syms (s)
  (setq count (1+ count)))
     ⇒ count-syms
(mapatoms 'count-syms)
     ⇒ nil
count
     ⇒ 1871

可参见 Access to Documentation Strings(访问文档)章节中关于 documentation 函数的说明,其中包含另一个使用 mapatoms 函数的示例。

Function: unintern symbol obarray

该函数将符号 symbol 从符号表 obarray 中移除。若 symbol 并未实际存在于该符号表中,unintern 不会执行任何操作。若 obarraynil,则使用当前的符号表。

如果你传入的不是符号,而是一个字符串作为 symbol 参数,该字符串会被视作符号名称。此时 unintern 会删除符号表中名称为此字符串的符号(若存在);若不存在该名称的符号,unintern 不会执行任何操作。

unintern 成功删除了符号,返回 t;否则返回 nil

Function: obarray-clear obarray

该函数会将符号表 obarray 中的所有符号全部移除。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike