Skip to content

LISP - 符号

在 Common Lisp 中,符号(symbol)是一个唯一的、命名的(named)数据对象。它不仅仅是一个变量名;符号本身就是一个可以被操作的对象。可以把它想象成一个命名的容器,可以同时容纳一个值、一个函数和一个属性列表(property list)。

符号的经典特性之一是属性列表(property list),或简称 plist。plist 是一个直接附加到符号上的简单键值存储(key-value store),允许你将任意元数据(metadata)与之关联。

属性列表的结构是一个由交替出现的键(称为 指示符,indicator)和值组成的列表。例如,如果我们想描述一个人,我们可以使用符号 'person 并附加 age、job 和 city 等属性。

当一个符号首次创建时,它的属性列表是空的。你可以使用 setf 表达式中的 get 函数来添加或修改属性。setf 是 Lisp 的通用赋值操作符(assignment operator),能够修改多种不同类型的位置。

让我们看看如何给名为 book 的符号分配属性。

创建一个名为 main.lisp 的新源文件,并输入以下代码。此示例演示了如何设置然后从 book 符号中检索属性。

;;; 使用 SETF 与 GET 来给 'book 符号分配属性。
;;; 注意,符号 ('book) 和指示符 ('title 等) 都需要引用。
(setf (get 'book 'title) "Gone with the Wind")
(setf (get 'book 'author) "Margaret Mitchell")
(setf (get 'book 'publisher) "Warner Books")
;;; 使用 GET 来检索特定属性的值。
(format t "Title: ~s~%" (get 'book 'title))
(format t "Author: ~s~%" (get 'book 'author))
;;; 如果属性不存在,GET 返回 NIL。
(format t "ISBN: ~s~%" (get 'book 'isbn))

当你加载并执行代码(例如,在 SBCL 的 REPL 中使用 (load "main.lisp"))时,它会产生以下结果:

Title: "Gone with the Wind"
Author: "Margaret Mitchell"
ISBN: NIL

Common Lisp 提供了一些用于管理属性列表的关键函数。

  • get symbol indicator &optional default:检索给定 indicator 的值。如果未找到,则返回 NIL 或提供的 default 值。
  • symbol-plist symbol:返回符号的整个属性列表。
  • remprop symbol indicator:从符号的属性列表中移除一个属性(包括键和值)。

让我们在前一个示例的基础上,看看这些函数的实际应用。

;; 定义一个函数来设置和演示符号的属性。
(defun demonstrate-plists (person-symbol)
(setf (get person-symbol 'age) 43)
(setf (get person-symbol 'job) "Accountant")
(setf (get person-symbol 'sex) 'female)
(format t "Initial plist for '~s: ~s~%"
person-symbol
(symbol-plist person-symbol))
;; 移除 'age' 属性
(remprop person-symbol 'age)
(format t "After removing 'age: ~s~%" (symbol-plist person-symbol)))
;; 使用符号 'annie 运行演示
(demonstrate-plists 'annie)
Initial plist for 'ANNIE: (SEX FEMALE JOB "Accountant" AGE 43)
After removing 'age: (SEX FEMALE JOB "Accountant")

现代最佳实践:属性列表与其他结构

Section titled “现代最佳实践:属性列表与其他结构”

虽然属性列表是 Lisp 的经典特性,但对于建模结构化的应用程序数据,现代 Common Lisp 提供了更好的替代方案:

  • 哈希表(Hash Tables):适用于键不一定是符号的通用键值映射(key-value mapping)。它们效率很高。 (make-hash-table)
  • 结构体(Structs):一种定义带有命名槽(named slots)的简单类对象结构(object-like structures)的轻量级方式。它们提供比属性列表更好的性能和类型检查。(defstruct)
  • CLOS 类(CLOS Classes):完整的 Common Lisp 对象系统(Common Lisp Object System)提供了强大的功能,如继承(inheritance)、多方法(multi-methods)和内省(introspection)。(defclass)

何时使用属性列表? 属性列表最适合用于将元数据 附加到符号本身,特别是在元编程或与编译器相关的任务中。对于在应用程序中表示书籍或用户等实体,出于简化目的,优先选择 defstruct,或根据更复杂的需求选择 defclass。

;; 定义一个书籍结构体
(defstruct book
title
author
publisher
isbn)
;; 创建一个书籍结构体的实例
(let ((my-book (make-book :title "Structure and Interpretation of Computer Programs"
:author "Abelson & Sussman"
:publisher "MIT Press")))
;; 访问槽(slots)清晰高效
(format t "Struct Title: ~s~%" (book-title my-book))
(format t "Struct Author: ~s~%" (book-author my-book)))
Struct Title: "Structure and Interpretation of Computer Programs"
Struct Author: "Abelson & Sussman"