0

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. 如何使槽延迟动态限定范围,使其值对函数可见(start-thread)
4

2 回答 2

2

首先要意识到在对象中拥有动态范围的槽没有好方法(除非实现有一些深层的魔法来支持这一点):唯一可行的方法是使用,本质上,显式浅绑定。例如,像这个宏这样的东西(这根本没有错误检查:我只是输入了它):

(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

但这是一种糟糕的方法,因为在存在多个线程的情况下,浅绑定很糟糕:任何其他想要查看foos 插槽的线程将看到临时值。所以这简直太可怕了。

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

这样做的一种方法是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 对象,因为在命名和公开内容等方面具有额外的灵活性(您几乎从不希望能够分配给其值是流动的插槽,例如:您想要分配体液)。

系统在幕后使用一个特殊变量来实现流体的深度绑定,因此可以与线程正常工作(即不同的线程可以对流体有不同的绑定)假设实现对特殊变量的处理是明智的(我确信所有多线程实现做)。

于 2022-02-11T18:40:33.770 回答
1

我不认为这是有道理的。变量有范围和范围,但值只是而槽只是值的一部分。此外,线程不继承动态绑定。

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

于 2022-02-11T16:12:44.070 回答