我发现在我使用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
之类的机制。
答案 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