12.17.2 定义新的 setf 形式

本节介绍如何定义可供 setf 操作的新形式。

Macro: gv-define-simple-setter name setter &optional fix-return

该宏可让你轻松为简单场景定义 setf 方法。name 为函数、宏或特殊形式的名称。 只要 name 存在与其直接对应的、用于更新它的 setter(赋值函数), 你就可以使用该宏,例如:(gv-define-simple-setter car setcar)

该宏会将如下形式的调用

(setf (name args...) value)

转换为

(setter args... value)

这类 setf 调用在文档中规定需返回 value(值)。对于 carsetcar 这类场景,这一点不会有问题,因为 setcar 本身就会返回它所设置的值。如果你的 setter(赋值函数)不会返回 value,请为 gv-define-simple-setterfix-return 参数传入非 nil 值。此时该宏会展开为等价于以下代码的形式:

(let ((temp value))
  (setter args... temp)
  temp)

以此确保最终返回正确的结果。

Macro: gv-define-setter name arglist &rest body

该宏支持定义比前文形式更复杂的 setf 展开逻辑。例如,当不存在可直接调用的简单赋值函数时, 或者虽存在赋值函数但该函数所需参数与位置形式的参数不一致时,你可能需要使用该宏。

该宏对 (setf (name args…) value) 形式的表达式进行展开时, 会先根据 arglist(参数列表)绑定 setf 的参数形式 (value args…), 随后执行 body(代码体)。body 应返回一个完成赋值操作的 Lisp 表达式, 且最终需返回被设置的值。以下是该宏的使用示例:

(gv-define-setter caar (val x) `(setcar (car ,x) ,val))
Macro: gv-define-expander name handler

若需要对展开过程进行更精细的控制,可使用 gv-define-expander 宏。例如,可读写的 substring 就可通过如下方式实现:

(gv-define-expander substring
  (lambda (do place from &optional to)
    (gv-letplace (getter setter) place
      (macroexp-let2* (from to)
        (funcall do `(substring ,getter ,from ,to)
                 (lambda (v)
                   (macroexp-let2* (v)
                     `(progn
                        ,(funcall setter `(cl--set-substring
                                           ,getter ,from ,to ,v))
                        ,v))))))))
Macro: gv-letplace (getter setter) place &rest body

gv-letplace 宏可用于定义行为类似 setf 的宏;例如,Common Lisp 中的 incf 宏就可通过如下方式实现:

(defmacro incf (place &optional n)
  (gv-letplace (getter setter) place
    (macroexp-let2* ((v (or n 1)))
      (funcall setter `(+ ,v ,getter)))))

getter 会被绑定为一个可复制的表达式,该表达式返回 place(位置)的值。 setter 会被绑定为一个函数,该函数接收一个表达式 v,并返回一个将 place 设为 v 的新表达式。 body(代码体)应返回一个通过 gettersetter 操作 place 的 Emacs Lisp 表达式。

更多细节可参考源码文件 gv.el

Function: make-obsolete-generalized-variable obsolete-name current-name when

该函数会让字节编译器发出警告,提示广义变量 obsolete-name(过时名称)已过时。 若 current-name 是符号类型,则警告信息会提示应使用 current-name 替代 obsolete-name; 若 current-name 是字符串类型,则该字符串会直接作为警告信息。 when 参数应为字符串类型,用于说明该变量首次被标记为过时的时间(通常为版本号字符串)。

Common Lisp 注意事项: Common Lisp 定义了另一种指定函数 setf 行为的方式, 即 setf 函数 — 这类函数的名称是列表形式 (setf name),而非符号。 例如,(defun (setf foo) …) 会定义一个函数,当 setf 作用于 foo 时会调用该函数。 Emacs 不支持这种方式:若对某个尚未定义对应展开逻辑的表达式使用 setf,会触发编译时错误。 而在 Common Lisp 中这并不会报错,因为 (setf func) 形式的函数可能在后续代码中被定义。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike