如何在方法上使用funcall来设置对象的属性

时间:2012-05-28 13:56:52

标签: oop common-lisp

考虑此代码:

(defclass test () ((test :initform nil :accessor test)))
#<STANDARD-CLASS TEST>
(defvar *test* (make-instance 'test))
*TEST*

和这个测试:

(funcall #'test *test*)
nil

人们会期望这有效:

(setf (funcall #'test *test*) 123)

相同
(setf (test *test*) 123)
123

但结果如下:

; in: LAMBDA NIL
;     (FUNCALL #'(SETF FUNCALL) #:NEW1175 #:TMP1177 #:TMP1176)
; ==>
;   (SB-C::%FUNCALL #'(SETF FUNCALL) #:NEW1175 #:TMP1177 #:TMP1176)
; 
; caught WARNING:
;   The function (SETF FUNCALL) is undefined, and its name is reserved by ANSI CL
;   so that even if it were defined later, the code doing so would not be portable.
; 
; compilation unit finished
;   Undefined function:
;     (SETF FUNCALL)
;   caught 1 WARNING condition

为什么它不起作用,我该如何解决它?

我使用SBCL和CLISP测试它的结果相同。

2 个答案:

答案 0 :(得分:5)

SETF是一种特殊形式(请参阅http://www.lispworks.com/documentation/HyperSpec/Body/05_aa.htm了解规范部分)。您的第二个示例有效,因为lisp实现在语法上解释(test *test*)

要查看正在发生的事情,请查看此会话:

This is SBCL 1.0.56.0.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (defclass test () ((test :initform nil :accessor test)))

#<STANDARD-CLASS TEST>
* (defvar *test* (make-instance 'test))

*TEST*
* (macroexpand '(setf (test *test*) 123))

(LET* ((#:*TEST*606 *TEST*))
  (MULTIPLE-VALUE-BIND (#:NEW605)
      123
    (FUNCALL #'(SETF TEST) #:NEW605 #:*TEST*606)))
T
* #'(setf test)

#<STANDARD-GENERIC-FUNCTION (SETF TEST) (1)>
* (macroexpand '(setf (funcall #'test *test*) 123))

(LET* ((#:G609 #'TEST) (#:*TEST*608 *TEST*))
  (MULTIPLE-VALUE-BIND (#:NEW607)
      123
    (FUNCALL #'(SETF FUNCALL) #:NEW607 #:G609 #:*TEST*608)))
T

请注意,第一个宏扩展会抓取#'(setf test),这是由defclass调用自动定义的编写器函数。第二个盲目转换为#'(setf funcall),它不存在(因此错误)。

回答你的“我该如何解决它?”问题,我们可能需要更多地了解您正在尝试做什么。例如,您可以使用(setf (slot-value object slot-name))之类的内容来允许您以编程方式选择插槽。

答案 1 :(得分:2)

:accessor插槽选项定义了两个函数:FOO用于读取插槽值,(SETF FOO)用于设置插槽值。请注意,在Common Lisp的后一种情况下,函数名称不是符号,而是列表。

如果你想要一个功能和值列表(你的评论),那么你的列表需要包含setter函数。

(defclass test ()
 ((foo :initform nil :accessor foo)
  (bar :initform nil :accessor bar)))

(map nil
     (lambda (function argument)
       (funcall function argument object))
     (list #'(setf foo) #'(setf bar))
     (list arg1 arg2))