我认为这不是很简单。你可以做这样的事情,它只适用于阅读(我使用了一个假的 toy 结构,所以我的代码可以工作,这里给出):
(defstruct toy
(first nil)
(second nil))
(defun foo (a-toy)
(symbol-macrolet ((x (or (toy-first a-toy) (toy-second a-toy))))
...))
但是现在(setf x ...) 是非常非法的。一旦你决定了(setf x ...) 应该做什么,你就可以通过定义一些本地函数来解决这个问题。我在这里决定它应该设置非nil 插槽,因为这对我来说很有意义。
(defun bar (a-toy)
(flet ((toy-slot (the-toy)
(or (toy-first the-toy) (toy-second the-toy)))
((setf toy-slot) (new the-toy)
(if (toy-first the-toy)
(setf (toy-first the-toy) new)
(setf (toy-second the-toy) new))))
(symbol-macrolet ((x (toy-slot a-toy)))
(setf x 2)
a-toy)))
现在您可以将这一切包装在一个宏中:
(defmacro binding-toy-slot ((x toy) &body forms)
(let ((tsn (make-symbol "TOY-SLOT")))
`(flet ((,tsn (the-toy)
(or (toy-first the-toy) (toy-second the-toy)))
((setf ,tsn) (new the-toy)
(if (toy-first the-toy)
(setf (toy-first the-toy) new)
(setf (toy-second the-toy) new))))
(symbol-macrolet ((,x (,tsn ,toy)))
,@forms))))
(defun bar (a-toy)
(binding-toy-slot (x a-toy)
(setf x 3)
a-toy))
显然,您可能想要概括 binding-toy-slot,因此它例如采用插槽访问器名称或类似名称的列表。
可能还有我没有想到的更好的方法:setf-expansions 可能有一些巧妙的技巧,可以让你在没有小辅助函数的情况下做到这一点。您还可以拥有 global 辅助函数,这些辅助函数通过一个对象和一个访问器列表来尝试,这将使代码稍微小一些(尽管您可以通过声明辅助函数在任何严肃的实现中实现类似的小代码inline 应该会导致它们被完全编译掉)。
另一种可能更好的方法是定义您想要使用泛型函数实现的协议。这意味着事物是全局定义的,它与 Kaz 的答案相关但并不完全相同。
再说一次,假设我有一些类(这可以是一个结构,但将其设置为成熟的 standard-class 可以让我们拥有未绑定的插槽,这很好):
(defclass toy ()
((first :initarg :first)
(second :initarg :second)))
现在您既可以定义名称为appropriate-slot-value 和(setf appropriate-slot-value) 的通用函数,也可以定义返回相应槽的名称 的GF,如下所示:
(define-condition no-appropriate-slot (unbound-slot)
;; this is not the right place in the condition heirarchy probably
()
(:report "no appropriate slot was bound"))
(defgeneric appropriate-slot-name (object &key for)
(:method :around (object &key (for ':read))
(call-next-method object :for for)))
(defmethod appropriate-slot-name ((object toy) &key for)
(let ((found (find-if (lambda (slot)
(slot-boundp object slot))
'(first second))))
(ecase for
((:read)
(unless found
(error 'no-appropriate-slot :name '(first second) :instance object))
found)
((:write)
(or found 'first)))))
现在访问函数对可以是普通函数,适用于任何有appropriate-slot-name 方法的类:
(defun appropriate-slot-value (object)
(slot-value object (appropriate-slot-name object :for ':read)))
(defun (setf appropriate-slot-value) (new object)
;; set the bound slot, or the first slot
(setf (slot-value object (appropriate-slot-name object :for ':write)) new))
最后,我们现在可以拥有只使用symbol-macrolet 的函数:
(defun foo (something)
(symbol-macrolet ((s (appropriate-slot-value something)))
... s ... (setf s ...) ...))
所以,这是另一种方法。