首先要意识到在对象中拥有动态范围的槽没有好方法(除非实现有一些深层的魔法来支持这一点):唯一可行的方法是使用,本质上,显式浅绑定。例如,像这个宏这样的东西(这根本没有错误检查:我只是输入了它):
(defmacro with-horrible-shallow-bound-slots ((&rest slotds) object &body forms)
(let ((ovar (make-symbol "OBJECT"))
(slot-vars (mapcar (lambda (slotd)
(make-symbol (symbol-name (first slotd))))
slotds)))
`(let ((,ovar ,object))
(let ,(mapcar (lambda (v slotd)
`(,v (,(first slotd) ,ovar)))
slot-vars slotds)
(unwind-protect
(progn
(setf ,@(mapcan (lambda (slotd)
`((,(first slotd) ,ovar) ,(second slotd)))
slotds))
,@forms)
(setf ,@(mapcan (lambda (slotd slot-var)
`((,(first slotd) ,ovar) ,slot-var))
slotds slot-vars)))))))
现在,如果我们有一些结构:
(defstruct foo
(x 0))
然后
(with-horrible-shallow-bound-slots ((foo-x 1)) foo
(print (foo-x foo)))
扩展到
(let ((#:object foo))
(let ((#:foo-x (foo-x #:object)))
(unwind-protect
(progn (setf (foo-x #:object) 1) (print (foo-x foo)))
(setf (foo-x #:object) #:foo-x))))
其中所有具有相同名称的 gensyms 实际上是相同的。所以:
> (let ((foo (make-foo)))
(with-horrible-shallow-bound-slots ((foo-x 1)) foo
(print (foo-x foo)))
(print (foo-x foo))
(values))
1
0
但这是一种糟糕的方法,因为在存在多个线程的情况下,浅绑定很糟糕:任何其他想要查看foo
s 插槽的线程也将看到临时值。所以这简直太可怕了。
一个好的方法是意识到虽然您不能安全地动态绑定对象中的插槽,但您可以通过使用秘密特殊变量来动态绑定该插槽索引的值来保存一堆绑定。在这种方法中,槽的值不会改变,但它们索引的值会改变,并且可以在存在多个线程的情况下安全地这样做。
这样做的一种方法是Tim Bradshaw 的fluids
玩具。其工作方式是您将插槽的值定义为流体,然后您可以绑定该流体的值,该绑定具有动态范围。
(defstruct foo
(slot (make-fluid)))
(defun outer (v)
(let ((it (make-foo)))
(setf (fluid-value (foo-slot it) t) v) ;set global value
(values (fluid-let (((foo-slot it) (1+ (fluid-value (foo-slot it)))))
(inner it))
(fluid-value (foo-slot it)))))
(defun inner (thing)
(fluid-value (foo-slot thing)))
这通常更适用于 CLOS 对象,因为在命名和公开内容等方面具有额外的灵活性(您几乎从不希望能够分配给其值是流动的插槽,例如:您想要分配体液)。
系统在幕后使用一个特殊变量来实现流体的深度绑定,因此可以与线程正常工作(即不同的线程可以对流体有不同的绑定)假设实现对特殊变量的处理是明智的(我确信所有多线程实现做)。