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

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

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


person Mark Karpov    schedule 22.06.2014    source источник
comment
Говорят. Где это сказано? в надежде, что они все равно будут привязаны к нулю - зачем вам надеяться на что-то, в то время как спецификация Common Lisp говорит что-то другое? «надеяться» на самом деле не очень полезный инструмент при работе с языками программирования. Наличие свободных слотов позволяет, например, определить, инициализированы они или нет.   -  person Rainer Joswig    schedule 22.06.2014
comment
@RainerJoswig, вы совершенно правы насчет «надеяться» ;-) Это было просто небольшое практическое изучение возможностей языка, прежде чем читать какие-либо документы.   -  person Mark Karpov    schedule 22.06.2014
comment
См.: gigamonkeys.com/book/object-reorientation-classes.html   -  person Rainer Joswig    schedule 22.06.2014
comment
Не только слоты и специальные переменные, но и символы могут быть освобождены как функции. См. fmakunbound и makunbound. Обратите внимание, что символ не обязательно должен иметь специальное объявление, чтобы иметь символьное значение.   -  person Joshua Taylor    schedule 22.06.2014


Ответы (2)


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

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

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

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

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

(defun set-all-unbound-slots (instance &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) &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
person Rainer Joswig    schedule 22.06.2014

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

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

Пример использования 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
person Xach    schedule 24.06.2014