7.5 宏字符 (Macro Characters)

一个宏字符 (macro character)是获得 read 特别待遇的字符。比如小写的 a ,通常与小写 b 一样处理,但一个左括号就不同了: 它告诉 Lisp 开始读入一个列表。

一个宏字符或宏字符组合也称作 read-macro (读取宏) 。许多 Common Lisp 预定义的读取宏是缩写。比如说引用 (Quote): 读入一个像是 'a 的表达式时,它被读取器展开成 (quote a) 。当你输入引用的表达式 (quoted expression)至顶层时,它们在读入之时就会被求值,所以一般来说你看不到这样的转换。你可以透过显式调用 read 使其现形:

  1. > (car (read-from-string "'a"))
  2. QUOTE

引用对于读取宏来说是不寻常的,因为它用单一字符表示。有了一个有限的字符集,你可以在 Common Lisp 里有许多单一字符的读取宏,来表示一个或更多字符。

这样的读取宏叫做派发 (dispatching)读取宏,而第一个字符叫做派发字符 (dispatching character)。所有预定义的派发读取宏使用井号 ( # )作为派发字符。我们已经见过好几个。举例来说, #'(function ...) 的缩写,同样的 '(quote ...) 的缩写。

其它我们见过的派发读取宏包括 #(...) ,产生一个向量; #nA(...) 产生数组; #\ 产生一个字符; #S(n ...) 产生一个结构。当这些类型的每个对象被 prin1 显示时 (或是 format 搭配 ~S),它们使用对应的读取宏 [2] 。这表示着你可以写出或读回这样的对象:

  1. > (let ((*print-array* t))
  2. (vectorp (read-from-string (format nil "~S"
  3. (vector 1 2)))))
  4. T

当然我们拿回来的不是同一个向量,而是具有同样元素的新向量。

不是所有对象被显示时都有着清楚 (distinct)、可读的形式。举例来说,函数与哈希表,倾向于这样 #<...> 被显示。实际上 #<...> 也是一个读取宏,但是特别用来产生当遇到 read 的错误。函数与哈希表不能被写出与读回来,而这个读取宏确保使用者不会有这样的幻觉。 [3]

当你定义你自己的事物表示法时 (举例来说,结构的印出函数),你要将此准则记住。要不使用一个可以被读回来的表示法,或是使用 #<...>