使用SETF函数来扩展SETF的工作原理是什么?

9

在《实用通用Lisp》第17章“面向对象重构:类” 的“访问者函数”一节中,我发现很难理解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)返回一个可SETF的slot-value:类bank-account中的customer-name,然后SETF用它来将值设置为"Sally Sue"吗?

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

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

  • 在上面的函数中,(setf customer-name)和函数体中的'customer-name是否引用同一内容?

  • 该部分说明:

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

    如果是这样的话,为什么在函数定义内部使用slot-value函数,而不是可以用来访问位置的函数呢?

2个回答

12

在访问和设置数据中,我们通常需要两个东西:

  • 从数据结构中检索某些内容的方法
  • 向数据结构中设置某些内容的方法

因此,人们需要定义一个 setter 函数和一个 getter 函数。对于简单情况,它们看起来也很简单。但是对于复杂情况可能并非如此。如果您知道 getter 的名称,那么 setter 的名称是什么?或者,如果您知道 setter 的名称,getter 的名称是什么?

Common Lisp 认为您只需要知道 getter 的名称。

  • 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 是一个变量,其值将绑定到 setter 的 account 变量。 "Sally Sue" 是一个字符串,它将绑定到 setter 的 name 变量。

作为开发者,您只需知道 getter:

  • getter 的使用方式: (customer-name my-account)

  • setter 的使用方式:(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 - 这次使用通过槽名访问槽值的访问方式。


那么(setf (customer-name my-account) "Sally Sue")调用了(setf-customer-name)函数?但是该函数需要两个参数,而只传递了一个参数"Sally Sue" - Bleeding Fingers
@BleedingFingers,“(setf customer-name)”是您的设置器函数。它的名称不仅是符号,而且是一个列表,因此并不明显,但是当您键入“(setf(customer-name my-account)”Sally Sue“)”时,您将调用您的设置器并传递两个参数:字符串” Sally Sue“和”bank-account“类的对象。 - Mark Karpov
1
这个特定的行为定义在哪里?如果account(setf customer-name)的第一个参数,name是第二个参数,它会起同样的作用吗?(看起来像某种黑魔法。) - Bleeding Fingers
3
setf 函数的语义在 HyperSpec 中定义。新值总是作为第一个参数传递给 setf 函数。 - Tim

2

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

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

为了

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

Practical Common Lisp中的文章在有点省略的情况下解释了在defclass类定义中指定:accessor选项时背后发生的事情。


谢谢你的回答,Tim。 - Bleeding Fingers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接