Почему слоты CLOS могут быть несвязанными?

#lisp #common-lisp #slot #clos

#lisp #common-lisp #слот #clos

Вопрос:

Говорят, что только специальные переменные в Common Lisp могут быть не привязаны. Для всех лексических переменных значение по умолчанию равно nil . Я думал, что слоты классов существуют в чем-то вроде closure, но, очевидно, это не так.

Если я определю слоты CLOS без :initform параметра (в надежде, что они все равно будут привязаны nil ) и не буду указывать значения при создании экземпляра, я получу экземпляр с несвязанными слотами. Почему это так? Это не удобно.

Комментарии:

1. «Это сказано». Где это сказано? «в надежде, что они все равно будут привязаны к нулю» — почему вы должны на что-то надеяться, в то время как спецификация Common Lisp говорит что-то другое? «надежда» на самом деле не является полезным инструментом при работе с языками программирования. Наличие несвязанных слотов позволяет, например, определить, инициализированы они или нет.

2. @RainerJoswig, Ты совершенно прав насчет «надежды» 😉 Это было просто небольшое практическое изучение возможностей языка, прежде чем читать какие-либо документы.

3. Видишь: gigamonkeys.com/book/object-reorientation-classes.html

4. Не только слоты и специальные переменные, но и символы могут быть не привязаны как функции. См . fmakunbound и makunbound . Обратите внимание, что символ не обязательно должен иметь специальное объявление, чтобы иметь значение символа.

Ответ №1:

Это очень удобно для таких вещей, как вычисление значений слотов по требованию, где значение иногда может быть равно нулю. При доступе к несвязанному слоту CLOS обычно вызывает SLOT-UNBOUND универсальную функцию, которая сигнализирует об ошибке. Однако вместо ошибки вы можете специализироваться SLOT-UNBOUND на вычислении и сохранении значения по требованию. Последующие обращения будут использовать значение слота напрямую, и вы можете очистить «кэш» слота с помощью SLOT-MAKUNBOUND .

Вы могли бы сделать это с помощью какого-либо «не привязанного» значения sentinel, но вместо этого очень удобно иметь встроенную функциональность.

Пример slot-unbound использования:

 (defclass foo ()
  ((bar :accessor bar)
   (baz :accessor baz)))

(defmethod slot-unbound (class (instance foo) slot-name)
  (declare (ignorable class))
  (setf (slot-value instance slot-name) nil))
  

В действии:

 CL-USER> (defparameter *foo* (make-instance 'foo))
*FOO*
CL-USER> (bar *foo*)
NIL
CL-USER> (setf (baz *foo*) (not (baz *foo*)))
T
  

Ответ №2:

Экземпляры CLOS не являются замыканиями

Экземпляры CLOS обычно не реализуются как замыкания. Это было бы сложно. Они представляют собой некоторую структуру данных с чем-то вроде вектора для слотов. Аналогично структурам Common Lisp. Разница между экземплярами CLOS и структурами усложняет это: экземпляры CLOS могут изменять количество слотов во время выполнения, и можно изменить класс экземпляра CLOS во время выполнения.

Убедитесь, что слоты имеют NIL значение

С помощью некоторых продвинутых CLOS вы можете убедиться, что слоты имеют нулевое значение. Обратите внимание, что функции в пакете CLOS в моем примере могут находиться в другом пакете в вашей CL.

Эта функция просматривает все слоты экземпляра. Если слот не привязан, для него устанавливается значение NIL .

 (defun set-all-unbound-slots (instance amp;optional (value nil))
  (let ((class (class-of instance)))
    (clos:finalize-inheritance class)
    (loop for slot in (clos:class-slots class)
          for name = (clos:slot-definition-name slot)
          unless (slot-boundp instance name)
          do (setf (slot-value instance name) value))
    instance))
  

Мы создаем класс mixin:

 (defclass set-unbound-slots-mixin () ())
  

Исправление будет запущено после инициализации объекта.

 (defmethod initialize-instance :after ((i set-unbound-slots-mixin) amp;rest initargs)
  (set-all-unbound-slots i nil))
  

Пример:

 (defclass c1 (set-unbound-slots-mixin)
  ((a :initform 'something)
   b
   c))


CL-USER 1 > (describe (make-instance 'c1))

#<C1 4020092AEB> is a C1
A      SOMETHING
B      NIL
C      NIL