LISP - 宏字符
Lisp - 读取宏和宏字符
Section titled “Lisp - 读取宏和宏字符”Common Lisp 最强大和独特的特性之一是其可扩展的解析器,称为读取器(reader)。宏字符(macro characters)是在读取阶段触发自定义解析逻辑的特殊字符,这种逻辑称为读取宏(reader macro)。这允许您扩展 Lisp 自身的语法,创建领域特定语言(DSLs)或便捷的速记符号。
Lisp 读取器的作用
Section titled “Lisp 读取器的作用”在 Lisp 代码被求值或编译之前,读取器会处理源代码文本并将其转换为 Lisp 对象(S-表达式)。当读取器遇到宏字符时,它不会将其视为普通字符读取。相反,它会调用关联的读取宏函数,该函数随后会接管控制权,从输入流中消耗后续文本,并返回一个 Lisp 对象。
您可能已经熟悉许多标准宏字符:
((左括号):开始读取列表。递归读取对象,直到找到匹配的)。)(右括号):终止列表读取。'(单引号):quote特殊形式的简写。'foo被读取为(quote foo)。;(分号):开始单行注释。读取器会忽略该行的其余部分。"(双引号):开始和结束读取字符串字面量。- ``` ` “(反引号/准引用号):启动一个准引用(quasiquote)模板,允许进行结构化代码生成并选择性地求值。
,(逗号):在反引号表达式内部使用,用于标记要进行求值的子表达式。#(井号/分发字符):一个分发宏字符(dispatching macro character)。它的作用取决于其后的字符(例如,#'用于函数对象,#\用于字符字面量,#(...)用于向量)。
定义自定义宏字符
Section titled “定义自定义宏字符”您可以使用 set-macro-character 定义自己的宏字符。此函数将一个字符与一个将由读取器执行的处理函数关联起来。
示例:一个类似 JSON 的对象读取器
Section titled “示例:一个类似 JSON 的对象读取器”让我们创建一个简单的读取宏。我们将使 { 成为一个宏字符,它能读取类似 {"key": "value"} 的 JSON 风格对象,并将其转换为 Lisp 哈希表。
;; 首先,定义读取器函数。它接受流和字符作为参数。(defun json-object-reader (stream char) (declare (ignore char)) ; 我们不需要字符 '{' 本身
;; 核心逻辑是读取一个键值对列表... (let ((pairs (read-delimited-list #\} stream t))) ;; ...并将其转换为创建哈希表的表达式。 `(let ((table (make-hash-table :test #'equal))) ,@(loop for (key value) in pairs collect `(setf (gethash ,key table) ,value)) table)))
;; 现在,将字符 '{' 与我们的读取器函数关联起来。(set-macro-character #\{ #'json-object-reader)
;; 我们还需要告诉读取器,字符 '}' 会关闭我们的形式。(set-macro-character #\} (get-macro-character #\) nil))
;; 让我们使用新语法!(let ((my-data '{"name" "John Doe" "age" 30})) (print (gethash "name" my-data)))- 当读取器看到
{时,它会调用json-object-reader。 (read-delimited-list #\} ...)会读取大括号内的所有形式,直到找到一个}。- 该函数使用反引号构造一段 Lisp 代码。这段代码在求值时将创建并填充一个哈希表。
- 读取器将原始的
{...}文本替换为这段生成的代码。 - 最后,Lisp 求值这段生成的代码,创建哈希表。
"John Doe"最佳实践和警告
Section titled “最佳实践和警告”尽管读取器功能极其强大,但修改它时应格外小心:
- 可读性:非标准语法可能使代码难以被他人(和您未来的自己)阅读和理解。
- 可移植性:过度使用自定义读取宏可能会将您的代码绑定到特定的环境或项目。
- 用例:它们最适合用于创建嵌入式 DSL 或在明确定义的上下文中,真正提高生产力的重要语法便利。
在现代实践中,通常更倾向于使用常规的 Lisp 宏(defmacro)来创建 DSL,因为它们作用于标准 Lisp 语法,并且更容易理解。读取宏仅在您确实需要改变语言基本语法时才应使用。