10.6 通用化引用 (Generalized Reference)

由于一个宏调用可以直接在它出现的地方展开成代码,任何展开为 setf 表达式的宏调用都可以作为 setf 表达式的第一个参数。 举例来说,如果我们定义一个 car 的同义词,

  1. (defmacro cah (lst) `(car ,lst))

然后因为一个 car 调用可以是 setf 的第一个参数,而 cah 一样可以:

  1. > (let ((x (list 'a 'b 'c)))
  2. (setf (cah x) 44)
  3. x)
  4. (44 B C)

撰写一个展开成一个 setf 表达式的宏是另一个问题,是一个比原先看起来更为困难的问题。看起来也许你可以这样实现 incf ,只要

  1. (defmacro incf (x &optional (y 1)) ; wrong
  2. `(setf ,x (+ ,x ,y)))

但这是行不通的。这两个表达式不相等:

  1. (setf (car (push 1 lst)) (1+ (car (push 1 lst))))
  2. (incf (car (push 1 lst)))

如果 lstnil 的话,第二个表达式会设成 (2) ,但第一个表达式会设成 (1 2)

Common Lisp 提供了 define-modify-macro 作为写出对于 setf 限制类别的宏的一种方法 它接受三个参数: 宏的名字,额外的参数 (隐含第一个参数 place),以及产生出 place 新数值的函数名。所以我们可以将 incf 定义为

  1. (define-modify-macro our-incf (&optional (y 1)) +)

另一版将元素推至列表尾端的 push 可写成:

  1. (define-modify-macro append1f (val)
  2. (lambda (lst val) (append lst (list val))))

后者会如下工作:

  1. > (let ((lst '(a b c)))
  2. (append1f lst 'd)
  3. lst)
  4. (A B C D)

顺道一提, pushpop 都不能定义为 modify-macros,前者因为 place 不是其第一个参数,而后者因为其返回值不是更改后的对象。