具有动态作用域槽的公共Lisp结构



Common Lisp在词汇范围内,但也可以使用(declare (special *var*))创建动态绑定。我需要的是一种创建动态范围结构槽的方法,它的值对所有其他槽都可见。这里有一个例子:

(defun start-thread ()
*delay*) ;; We defer the binding of *delay*

这适用于一个常见的词汇环境:

(let ((*delay* 1))
(declare (special *delay*))
(start-thread)) ;; returns 1

这不起作用:

(defstruct table  
(*delay* 0)
(thread (start-thread)))
(make-table) ;; => Error: *delay* is unbound.

我的问题是

  1. 如何从其他插槽引用插槽延迟
  2. 如何使slotdelay动态作用域,使其值可见对于函数(start-thread)

首先要意识到,在对象中没有一个好的方法可以动态确定范围的槽(除非实现有一些强大的魔力来支持这一点(:唯一有效的方法是使用显式浅绑定。例如,类似于这个宏的东西(这根本没有错误检查:我只是输入了它(:

(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))))

其中所有具有相同名称的同义词实际上是相同的。因此:

> (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的槽的线程都会看到临时值。所以这太可怕了。

然后,一个好的方法是意识到,虽然您不能安全地动态绑定对象中的插槽,但可以通过使用一个秘密特殊变量来保存绑定堆栈来动态绑定该插槽索引的值。在这种方法中,槽的值不会改变,但它们索引的值会改变,并且可以在存在多个线程的情况下安全地这样做。

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对象,因为在命名和公开内容等方面具有额外的灵活性(您几乎从不希望能够指定给值为流体的槽,例如:您希望指定流体的值(。

该系统在后台使用一个特殊的变量来实现流体的深度绑定,因此如果实现合理地处理特殊变量(我相信所有多线程实现都会这样做(,它将与线程一起正常工作(即不同的线程可以对流体有不同的绑定(。

我认为这没有意义。变量有作用域和范围,但值就是,槽只是值的一部分。此外,线程不会继承动态绑定。

如果你想让动态地更改某种对象(可以这么说(,你需要把它作为一个整体值放入一个动态变量中,并用修改后的版本进行重新绑定(最好是基于一些不变性,即持久数据结构,例如FSet(。

我在这里猜测您需要什么,但我认为使用类和初始化实例会得到您想要的。在下面的代码中,我将您的结构重写为一个类,并在对(make-instance'table(的调用中传递对象本身来初始化实例。

(defclass table ()
((delay :initform 5)
(thread)))
(defun start-my-thread (obj)
(print (slot-value obj 'delay)))
(defmethod initialize-instance :after ((obj table) &key)
(start-my-thread obj))
(make-instance 'table)
; above call will print 5

最新更新