Как работает функция SETF для расширения SETF?

В главе Practical Common 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) значение слота SETFable customer-name класса bank-account, которое затем SETF использует для установки значения «Салли Сью»?

  • действительно ли (setf (customer-name my-account) "Sally Sue") вызывает функцию выше?

  • как определено выше, является ли setf customer-name функцией?

  • в приведенной выше функции customer-name в (setf customer-name) и 'customer-name в теле относятся к одному и тому же?

  • #P9# <блочная цитата> #P10# #P11#

person Bleeding Fingers    schedule 06.06.2014    source источник


Ответы (2)


Во многих случаях для доступа и настройки данных нужны две вещи:

  • способ получить что-то из структуры данных
  • способ установить что-то в структуре данных

Таким образом, можно было бы определить функцию установки и функцию получения. Для простых случаев они также могут выглядеть простыми. Но для сложных случаев их может и не быть. Теперь, если вы знаете имя геттера, каково имя сеттера? Или: если вы знаете имя сеттера, как зовут геттера?

В Common Lisp есть идея, что вам нужно знать только имя геттера.

  • геттер называется, скажем, GET-FOO

  • то функция установки называется (SETF GET-FOO). Всегда.

  • функцию установки можно вызвать следующим образом: (setf (get-foo some-bar) new-foo). Всегда.

Итак, вы пишете функцию GET-FOO. Вы также пишете функцию (SETF GET-FOO), и Common Lisp регистрирует ее как функцию установки.

(SETF GET-FOO) — это список. Это также имя функции. Здесь у нас есть исключение: Common Lisp иногда позволяет использовать список в качестве имени функции. Таким образом, не все имена функций являются символами, некоторые из них на самом деле являются списками.

(setf (customer-name my-account) "Sally Sue") на самом деле является вызовом определенного сеттера. my-account — это переменная, значение которой будет привязано к переменной account установщика. "Sally Sue" — это строка, и она будет привязана к переменной name установщика.

Как разработчику вам нужно знать только геттер:

  • использование геттера: (customer-name my-account)

  • использование сеттера: (setf (customer-name my-account) "Sally Sue"). SETF — это макрос, который расширяется до вызова функции установки.

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

Above defines a setter function called (setf customer-name).

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

Когда функция вызывается через макрос SETF, она вызывает другой сеттер — на этот раз используя доступ к значению слота через имя слота.

person Rainer Joswig    schedule 06.06.2014
comment
Итак, (setf (customer-name my-account) "Sally Sue") звонит (setf-customer-name)? Но эта функция ожидает два аргумента, и передается только один, "Sally Sue". - person Bleeding Fingers; 07.06.2014
comment
@BleedingFingers, (setf customer-name) - это ваша функция установки. Его имя не просто символ, а список, так что это не очевидно, но когда вы вводите (setf (customer-name my-account) "Sally Sue"), вы вызываете свой сеттер и передаете два аргумента: строку Салли Сью и объект класса bank-account. - person Mark Karpov; 07.06.2014
comment
Где определяется это конкретное поведение? Если бы account был первым аргументом для (setf customer-name), а name вторым, работало бы это так же? (Похоже на какую-то черную магию.) - person Bleeding Fingers; 07.06.2014
comment
Семантика setf функций определена в HyperSpec. Новое значение всегда передается в качестве первого аргумента функции setf. - person Tim; 11.06.2014

setf — это очень сложный макрос, который знает, как декодировать свой первый аргумент, который обычно выглядит как вызов функции, как «место», а затем вызывает любые формы, необходимые для установки нового значения места. Бесполезно думать о (customer-name my-account) как о возвращении чего-либо в выражении setf. Макрос setf применяет правила, определенные в HyperSpec, к своей форме места. и по умолчанию преобразует

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

to

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

Отрывок из Practical Common Lisp несколько абстрактно объясняет, что происходит за кулисами, когда вы указываете опцию :accessor в определении слота defclass.

person Tim    schedule 11.06.2014
comment
Спасибо за ответ Тим. - person Bleeding Fingers; 13.06.2014