【问题标题】:Evaluation of setf forms评估 setf 表格
【发布时间】:2017-02-18 19:02:10
【问题描述】:

这个问题是关于 Common Lisp setf 宏,以及它如何评估它的参数形式(和子形式)——也就是说,如果它们碰巧出现不止一次,则只有一次。 (这也是对 Using get-setf-expansion 的 cmets 中给出的示例的部分跟进。)

;create a list of two hash tables
* (defparameter hts (list (make-hash-table) (make-hash-table)))
HTS
* hts
(#<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>)

;define a function that swaps the position of the two hash tables
* (defun next-ht (hts) 
    (rotatef (first hts) (second hts))
    (second hts))
NEXT-HT

交换:

;now do a swap to verify it works
* (next-ht hts)
#<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>
* hts
(#<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>
 #<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>)

;and swap them back
* (next-ht hts)
#<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>
* hts
(#<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>)

进一步测试:

;then set different values for a key in each table
* (setf (gethash 0 (first hts)) 11)
11
* (setf (gethash 0 (second hts)) 22)
22
* hts
(#<HASH-TABLE :TEST EQL :COUNT 1 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 1 {1007F77103}>)

;finally execute a setf with a swapping side-effect
* (setf (gethash 0 (next-ht hts)) (1+ (gethash 0 (next-ht hts))))
23

;but it looks like hts has been swapped twice
;back to its original state
* hts
(#<HASH-TABLE :TEST EQL :COUNT 1 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 1 {1007F77103}>)

;also, where did the initial value of 11 go?
* (gethash 0 (first hts))
23
T
* (gethash 0 (second hts))
22
T
*

有人可以澄清发生了什么吗?还有,带有副作用的setf表达式是什么意思?

【问题讨论】:

  • SETF 不会阻止对子表单进行两次评估。修改像 INCF 这样的宏。
  • @jkiiski 这就是我一直在寻找的区别。我以前的印象是setf 某种修改宏,因为该位置只是传递给get-setf-expansion。谢谢。

标签: common-lisp expression-evaluation setf


【解决方案1】:

为什么不对setf 表单进行宏展开?这里是 LispWorks:

CL-USER 32 > (pprint (macroexpand '(setf (gethash 0 (next-ht hts))
                                         (1+ (gethash 0 (next-ht hts))))))

(LET* ((#:|key1014| 0)
       (#:|table1015| (NEXT-HT HTS))
       (#:|default1016| NIL)
       (#:|store1017| (1+ (GETHASH 0 (NEXT-HT HTS)))))
  (SYSTEM::%PUTHASH #:|key1014| #:|table1015| #:|store1017|))

它有什么作用?

  • 获取键值
  • 获取哈希表,调用NEXT-HT
  • 获取默认值,未使用
  • 获取新值,调用NEXT-HT
  • 使用一些特定于实现的方式将新的键/值存储到哈希表中

很明显NEXT-HT 被调用了两次。

它背后的粗略(!)概念模型是什么?

  • setf 将检查第一个表达式。
  • 这是什么?哦,是gethash,让我为它设置setter表单
  • 然后,setter 表单将从第一个表单评估必要的子表单
  • 然后它将计算新值
  • 将使用这些参数调用 setter 运算符。

例子:

CL-USER 62 > (setf (gethash (print 0)
                            (print (next-ht hts))
                            (print 1))
                   (print (1+ (print (gethash 0
                                              (print (next-ht hts))
                                              2)))))

0 
#<EQL Hash Table{1} 402000137B> 
1 
#<EQL Hash Table{0} 4020001573> 
2 
3 
3   ; return value

【讨论】:

  • 感谢您总结setf 评估模型。但我试图更好地理解(参考上面的代码审查帖子)是为什么 acelent 明确建议使用 setf 表达式而不是使用 get-setf-expansion 编写自己的修改宏。这是因为您自己必须小心只评估一次子表单; get-setf-expansion 不会为您执行此操作。这是正确的吗?它可能有点“离题”,但我希望看到get-setf-expansion 操作的类似摘要。
  • 我花了一些时间来挖掘上面代码审查帖子中的信息,现在我意识到我真的不需要问这个问题。大多数答案已经存在。如果您有任何要添加的内容,请随意删除它,或将其用于进一步的教育目的。但感谢您的时间、精力和澄清。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-04
  • 1970-01-01
  • 1970-01-01
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-25
相关资源
最近更新 更多