setf 宏 ¶setf 宏是操作广义变量最基础的方式。setf 表达式与 setq 类似,
不同之处在于:它允许每一对参数的第一个(左侧)参数是任意合法的位置形式,而非仅能使用符号。
例如,(setf (car a) b) 会将 a 的 car 部分设为 b,其执行效果与
(setcar a b) 完全一致,但无需为这类位置的赋值和读取操作分别使用两个不同的函数。
该宏会对 form(表达式)进行求值,并将求值结果存储到 place(位置)中,
其中 place 必须是合法的广义变量形式。若传入多组 place 和 form 配对参数,
赋值操作会像 setq 一样按顺序执行。setf 的返回值为最后一个 form 的求值结果。
以下 Lisp 表达式是 Emacs 中可作为广义变量使用的形式,因此可出现在 setf 的 place 参数中:
(setf x y) 与 (setq x y) 完全等价;严格来讲,
既然有了 setf,setq 本身已属冗余。不过出于风格和历史原因,
大多数程序员仍倾向于使用 setq 来设置普通变量。宏 (setf x y)
实际上会展开为 (setq x y),因此在编译后的代码中使用它不会产生性能损耗。
aref cddr symbol-function car elt symbol-plist caar get symbol-value cadr gethash cdr nth cdar nthcdr
alist-get overlay-start default-value overlay-get face-background process-buffer face-font process-filter face-foreground process-get face-stipple process-sentinel face-underline-p terminal-parameter file-modes window-buffer frame-parameter window-dedicated-p frame-parameters window-display-table get-register window-hscroll getenv window-parameter keymap-parent window-point match-data window-start overlay-end
(substring subplace n [m]) 的调用——其中 subplace
本身是一个合法的广义变量,且其当前值为字符串类型,同时待存储的值也必须是字符串。
新字符串会被拼接至目标字符串的指定位置。例如:
(setq a (list "hello" "world"))
⇒ ("hello" "world")
(cadr a)
⇒ "world"
(substring (cadr a) 2 4)
⇒ "rl"
(setf (substring (cadr a) 2 4) "o")
⇒ "o"
(cadr a)
⇒ "wood"
a
⇒ ("hello" "wood")
if 和 cond 也可作为广义变量使用。例如,以下代码会将 foo
或 bar 变量赋值为 zot:
(setf (if (zerop (random 2))
foo
bar)
'zot)
若传入的 place(位置)形式是 setf 无法处理的类型,setf 会触发错误。
需要注意的是,对于 nthcdr 而言,该函数的列表参数本身必须是合法的 place(位置)形式。
例如,(setf (nthcdr 0 foo) 7) 会将 foo 本身赋值为 7。
宏 push(see 修改列表变量)和 pop(see 访问列表元素)
可操作广义变量,而非仅能操作列表。(pop place) 会移除并返回存储在 place 中的列表的第一个元素,
其功能类似于 (prog1 (car place) (setf place (cdr place))),
区别在于它会确保所有子表达式仅求值一次。
(push x place) 会将 x 插入到存储在 place 中的列表的头部,
其功能类似于 (setf place (cons x place)),差异同样体现在子表达式的求值逻辑上。
需注意,在 nthcdr 类型的位置上使用 push 和 pop,
可实现在列表任意位置插入或删除元素。
cl-lib 库为广义变量定义了多种扩展功能,包括新增的 setf 位置形式。
See Generalized Variables in Common Lisp Extensions, Common Lisp 扩展。