8

Practical Common Lisp17 章 Object Reorientation: Classes section Accessor Functions中,我发现很难理解SETF扩展的方式。

功能:

(defun (setf customer-name) (name account)
  (setf (slot-value account 'customer-name) name))

bank-account类定义:

(defclass bank-account ()
  ((customer-name
    :initarg :customer-name
    :initform (error "Must supply a customer name."))
   (balance
    :initarg :balance
    :initform 0)
   (account-number
    :initform (incf *account-numbers*))
   account-type))

我不明白的是:

  • 在表达式(setf (customer-name my-account) "Sally Sue")中确实(customer-name my-account)返回类的 SETFable 插槽值customer-namebank-account然后SETF用于将值设置为“Sally Sue”?

  • 实际上是(setf (customer-name my-account) "Sally Sue")在调用上面的函数吗?

  • 如上定义是setf customer-name一个函数?

  • 上面的函数customer-namein(setf customer-name)'customer-namein 是指同一个东西吗?

  • 该部分指出

    第二个元素是一个符号,通常是用于访问 SETF 函数将设置的位置的函数的名称

    如果是这种情况,那么slot-value当函数可用于访问该位置时,为什么还要在函数定义中使用该函数?

4

2 回答 2

10

在许多情况下访问和设置数据,需要做两件事:

  • 一种从数据结构中检索某些东西的方法
  • 一种在数据结构中设置某些东西的方法

因此,可以定义一个 setter 函数和一个 getter 函数。对于简单的情况,它们也可能看起来很简单。但对于复杂的情况,他们可能不会。现在,如果您知道 getter 的名称,那么 setter 的名称是什么?或者:如果你知道 setter 的名字,那么 getter 的名字是什么?

Common Lisp 的想法是您只需要知道 getter 的名称。

  • 吸气剂被称为,说,GET-FOO

  • 然后调用 setter 函数(SETF GET-FOO)。总是。

  • 可以通过这种方式调用 setter 函数:(setf (get-foo some-bar) new-foo). 总是。

所以你编写GET-FOO函数。您还编写了该(SETF GET-FOO)函数,Common Lisp 将其注册为一个 setter 函数。

(SETF GET-FOO)是一个列表。它也是一个函数的名称。这里有一个例外:Common Lisp 有时允许将列表作为函数名。因此,并非所有函数名称都是符号,有些实际上是列表。

(setf (customer-name my-account) "Sally Sue")实际上是调用定义的setter。my-account是一个变量,其值将绑定到accountsetter 的变量。"Sally Sue"是一个字符串,它将绑定到namesetter 的变量。

作为开发人员,您只需要了解 getter:

  • 吸气剂的使用: (customer-name my-account)

  • 设置器的使用:(setf (customer-name my-account) "Sally Sue"). SETF是一个宏,它扩展为 setter 函数的调用。

(defun (setf customer-name) (name account)
  (setf (slot-value account 'customer-name) name))

上面定义了一个名为(setf customer-name).

CL-USER 80 > (function (setf customer-name))
#<interpreted function (SETF CUSTOMER-NAME) 40A00213FC>

当通过SETF宏调用该函数时,它会调用另一个 setter - 这次使用通过插槽名称访问插槽值。

于 2014-06-06T21:32:08.597 回答
2

setf是一个非常复杂的宏,它知道如何将其第一个参数(通常看起来像一个函数调用)解码为一个“位置”,然后调用任何必要的形式来将该位置设置为新值。认为(customer-name my-account)返回setf表达式中的任何内容是没有用的。该宏将HyperSpecsetf中定义的规则应用于其位置形式,并且作为默认情况,将转换

(setf (foo arg0 arg1 ...) new-val)

(funcall #'(setf foo) new-val arg0 arg1 ...)

Practical Common Lisp中的段落以一种有点省略的方式解释了当您在插槽定义中指定:accessor选项时在幕后发生的事情。defclass

于 2014-06-11T13:02:10.773 回答