使用setf避免冗余位置计算

时间:2014-02-25 07:59:48

标签: common-lisp

上下文

我发现在我使用Common Lisp的有限经验中,有一些像这样的代码并不罕见

(setf (gethash key table)
      (my-transformation (gethash key table)))

setf位置读取的地方,对存储在那里的值执行一些计算,然后写入相同的位置。我不喜欢的事情是这个地方将被计算两次,但这个地方(希望!)两次都是相同的。如果地点计算很昂贵,我们所做的工作量就是我们需要的两倍。

问题

是否可以消除双重计算的需要?也就是说,是否可以(希望?)编写宏setf-inplace

(setf-inplace (gethash key table) #'my-transformation)

在概念上等效(暂时忽略multiple-values奇怪),但可能比原始代码快得多,最好不依赖于实现细节?

我知道我可能会在这个特殊情况下将购物车放在马前,SBCL caches the lookup for gethash,但在我看来,其他setf能够的地方,如(assoc key my-alist),缓存可能不那么容易 - 当然,除了上面的setf-inplace之类的机制。

1 个答案:

答案 0 :(得分:3)

(setf (gethash key table)
      (my-transformation (gethash key table)))

让我们说转换是1+。上面是

(setf (gethash key table)
      (1+ (gethash key table)))

Common Lisp中有一个宏:

(incf (gethash key table))

Clozure CL将此扩展为:

(LET* ((#:G69020 KEY)
       (#:G69021 TABLE)
       (#:G69022 1)
       (#:G69019 (+ (GETHASH #:G69020 #:G69021) #:G69022)))
  (DECLARE (TYPE BIT #:G69022) (TYPE T #:G69019))
  (CCL::PUTHASH #:G69020 #:G69021 #:G69019))

现在请记住,在编译时找到一个场所的setter,并且一个场所的setter操作与getter不同。通过更新某些东西不仅可以使setter有所不同,而且还可以执行任意其他操作,例如设置事务,获取锁定,......因此获取和设置不是相同/类似操作两次。

Common Lisp没有代表一个地方的物理数据结构。一个地方是一个抽象的想法,它将一个setter与一个给定的getter结合起来。 setter可以是任意复杂的代码。

那么,在上面的代码中 - 哪些计算是多余的?

您仍然可以说优化原始案例的访问权限:

(incf (car (very-long-access-chain data)))

你必须手写:

(let ((cons-cell (very-long-access-chain data)))
  (incf (car cons-cell))

这基本上就是Lisp所做的。请参阅:CLHS 5.1.1.1 Evaluation of Subforms to Places