在闭包中为函数定义setf

时间:2014-11-09 15:02:15

标签: lisp closures common-lisp

如果我创建这样的闭包,

(let ((A (make-array '(10) :initial-element 5)))    
  (defun h (i)
    (aref a i))
  (defsetf h (i) (x) `(setf (aref ,a ,i) ,x)))

然后,正如我所料,(h i)将返回a的第i个元素:

(h 1)  ;; => 5
(h 2)  ;; => 5

虽然setf扩展部分可以正常工作并正确设置a的第i个元素,但它也会在SBCL中产生警告:

(setf (h 1) 10)

; in: SETF (H 1)
;     (SETF (AREF #(5 10 5 5 5 5 5 5 5 5) 1) #:G1124)
; --> LET* MULTIPLE-VALUE-BIND LET FUNCALL SB-C::%FUNCALL 
; ==>
;   ((SETF AREF) #:NEW0 #(5 10 5 5 5 5 5 5 5 5) 1)
; 
; caught WARNING:
;   Destructive function (SETF AREF) called on constant data.
;   See also:
;     The ANSI Standard, Special Operator QUOTE
;     The ANSI Standard, Section 3.2.2.3
; 
; compilation unit finished
;   caught 1 WARNING condition

在GCL中发出错误信号:

>(setf (h 1) 10)

Error: 
Fast links are on: do (si::use-fast-links nil) for debugging
Signalled by LAMBDA-CLOSURE.
Condition in LAMBDA-CLOSURE [or a callee]: INTERNAL-SIMPLE-UNBOUND-VARIABLE: Cell error on A: Unbound variable: 

Broken at LIST.  Type :H for Help.
    1  Return to top level. 

在CLISP和ECL中,该示例工作正常。

我在编写Scheme几年后回到Common Lisp,所以我可能在概念上混合使用这两种语言。我想我已根据规范触发了未定义的行为,但我无法确切地看到我做错了什么。我很感激任何帮助!

1 个答案:

答案 0 :(得分:2)

您的问题

尝试macroexpand通常很有启发性:

(macroexpand '(setf (h 2) 7))
==>
(LET* ()
  (MULTIPLE-VALUE-BIND (#:G655)
      7
    (SETF (AREF #(5 5 5 5 5 5 5 5 5 5) 2) #:G655)))

正如您所看到的,您的setf调用会扩展为一个在文字数组上调用setf的表单,这通常是一个坏主意,事实上,这正是SBCL警告您的约:

Destructive function (SETF AREF) called on constant data.

请注意,尽管警告SBCL(以及CLISP和ECL等其他符合要求的实现)仍会按照您的预期执行操作。 这是因为文字数组由函数h可访问的局部变量引用。

解决方案

我建议您改用函数

(let ((A (make-array '(10) :initial-element 5)))
  (defun h (i)
    (aref a i))
  (defun (setf h) (x i)
    (setf (aref a i) x)))