【问题标题】:How does using the SETF function to extend SETF work?使用 SETF 功能扩展 SETF 是如何工作的?
【发布时间】:2014-06-06 21:03:05
【问题描述】:

Practical Common Lisp 章节 17. Object Reorientation: Classes 部分 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) 是否返回类bank-account 的可设置槽值customer-name,然后SETF 使用该值将值设置为“Sally Sue”?

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

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

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

  • 部分说明

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

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

【问题讨论】:

    标签: lisp common-lisp setf


    【解决方案1】:

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

    • 一种从数据结构中检索内容的方法
    • 一种在数据结构中设置内容的方法

    因此可以定义一个 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)的setter函数。

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

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

    【讨论】:

    • 所以(setf (customer-name my-account) "Sally Sue") 调用(setf-customer-name)?但是该函数需要两个参数,并且只有一个 "Sally Sue" 被传递。
    • @BleedingFingers, (setf customer-name) 是你的设置函数。它的名字不仅仅是一个符号,而是一个列表,所以它并不明显,但是当你输入(setf (customer-name my-account) "Sally Sue") 时,你会调用你的setter 并传递两个参数:字符串“Sally Sue”和bank-account 类的对象。
    • 这个特定的行为是在哪里定义的? account 是否是 (setf customer-name)name 第二个参数的第一个参数,它的工作方式是否相同? (看起来像是某种黑魔法。)
    • setf 函数的语义在HyperSpec 中定义。新值始终作为第一个参数传递给 setf 函数。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 感谢蒂姆的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-14
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-09
    相关资源
    最近更新 更多