LINUX.ORG.RU

Почему в CLOS только standard-class является экземпляром самого себя?

 , ,


1

3

По метаобъектным системам информацию днём с огнём не сыщешь (что доказывает их нужность :trollface:). Книг тоже меньше, чем три с половиной (или их тоже хрен найдёшь). Да и вообще, складывается такое впечатление, что за пределами Common Lisp к метауровню объектных систем относятся исключительно как «Ух ты, класс класса» — как к забавной, но не глобально продуманной идее. Ими можно пользоваться, чтобы добавить метод-другой, но они не позволяют действительно влиять на объектную систему. Разве что в Руби через призму Гугла и Stack Overflow видится некоторое шевеление на эту тему, но у них там какой-то свой особенный хипстерский мирок, в который я пока не вник.

Так это, я нахожу несколько странным вывод следующей программы:

(defclass extended-class (standard-class)
  (additional-slot) )
 
(defclass extended-object ()
  ()
  (:metaclass extended-class) )

(defun print-metaclass-slot-names (class-name)
  (let* ((metaclass (class-of (find-class class-name)))
         (metaclass-slots (class-slots metaclass))
         (slot-names (mapcar #'slot-definition-name metaclass-slots)) )
    (format t "~a: ~a~%" class-name slot-names) ) )
 
(print-metaclass-slot-names 'standard-class)
(print-metaclass-slot-names 'standard-object)
(print-metaclass-slot-names 'extended-class)
(print-metaclass-slot-names 'extended-object)
В CLOS отношение class-of по сути определяет набор слотов объектов. Почему стандартный класс метаобъектов сам определяет свой набор слотов, а пользовательские классы метаобъектов так делать не могут?

Например, я хочу создать иерархию наследования, независимую от существующей. Пусть в ней все классы будут обладать дополнительным слотом abstract, который говорит, применима ли к ним make-instance. То есть я такой создаю свой abstractable-class с этим слотом, его экземпляр называю abstractable-object — это альтернативный корень иерархии наследования, у которого abstract = nil. Но сам метаобъект класса abstractable-class не имеет этого слота, чем нарушает мою хотелку «все классы (новой иерархии) имеют слот abstract».

★★★

В CLOS отношение class-of по сути определяет набор слотов объектов

Не только и не столько. Слоты это все вторично и делается уже самим MOP. class-of определяет протокол инициализации скорее. Короче почитай AMOP.

Почему стандартный класс метаобъектов сам определяет свой набор слотов, а пользовательские классы метаобъектов так делать не могут?

Почему не могут? Ты это только что выше написал. Суть в том что в CLOS в рекурсию да, может только standard-class. Если почитаешь AMOP - поймешь почему. Но так а нахрена тебе рекурсия? TAPL обчитался? На, держи вот без рекурсии. Можно не благодарить.

(defclass abstract-class-mixin (standard-class)
  ((abstract :initform nil
             :initarg :abstract
             :reader abstract-p)))

(defmethod validate-superclass ((sc abstract-class-mixin) (c standard-class))
  t)

(defmethod validate-superclass ((c standard-class) (sc abstract-class-mixin))
  t)

(defclass std-class (abstract-class-mixin)
  ()
  (:metaclass abstract-class-mixin))

(defmethod allocate-instance ((class std-class) &rest args)
  (declare (ignore args))
  (if (abstract-p class)
    (error "Unable to allocate instance for abstract class ~s" (class-name class))
    (call-next-method)))

(defclass abstract-class (std-class)
  ()
  (:metaclass std-class)
  (:default-initargs :abstract t))

(defclass std-object ()
  ()
  (:metaclass std-class))

(defmacro define-class (name (&rest scs) (&rest slots) &rest opts)
  `(defclass ,name (,@scs std-object)
     ,slots
     ,@opts
     (:metaclass std-class)))

(defmacro define-abstract-class (name (&rest scs) (&rest slots) &rest opts)
  `(defclass ,name (,@scs std-object)
     ,slots
     ,@opts
     (:metaclass abstract-class)))

(define-abstract-class my-abstract-object ()
  ((slot :initform 'value)))

(define-class my-object (my-abstract-object)
  ())

;; CL-USER> (make-instance 'my-abstract-object)
;;   ==>
;; Unable to allocate instance for abstract class MY-ABSTRACT-OBJECT
;; [Condition of type SIMPLE-ERROR]

;; CL-USER> (describe (make-instance 'my-object))
;;   ==>
;; #<MY-OBJECT {258CF469}>
;;   [standard-object]
;; 
;; Slots with :INSTANCE allocation:
;;   SLOT  = VALUE

lovesan ★★
()

Посмотри cl-gtk2, там этих метаклассов выше крыши. В glib/gobject.meta.lisp

Там что ты хочешь

anonymous
()
Ответ на: комментарий от lovesan

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

Только ничего не вышло. Тогда я твёрдо сказал себе: «нахуй» и плюнул на все эти удивительные возможности MOP. Всё равно на практике мне ни разу не пригодилось что-то кроме обычных классов и дженериков.

А ты не напишешь решение?

anonymous
()
Ответ на: комментарий от anonymous
(defclass singleton-class () ;;метакласс, реализующий механизм синглтона
  ((instance :initform nil)))
 
(defmethod validate-superclass ((class singleton-class) (superclass standard-class))
  t) ;;Разрешаем наследование классов-синглтонов от обычных классов
 
(defmethod validate-superclass ((class singleton-class) (superclass singleton-class))
  t) ;;Разрешаем наследование классов-синглтонов от других классов-синглтонов
 
(defmethod validate-superclass ((class standard-class) (superclass singleton-class))
  nil) ;;Запрещаем наследование обычных классов от синглтонов
 
(defmethod make-instance ((class singleton-class) &key) 
  (with-slots (instance) class
    (or instance (setf instance (call-next-method)))))
 
(defclass my-singleton-class () 
  () 
  (:metaclass singleton-class))
monk ★★★★★
()

Разве что в Руби через призму Гугла и Stack Overflow...

Вот для мейнстримного питона:

class Singleton(type):
     def __init__(cls, name, bases, dict):
         super(Singleton, cls).__init__(name, bases, dict)
         cls.instance = None
     def __call__(cls,*args,**kw):
         if cls.instance is None:
             cls.instance = super(Singleton, cls).__call__(*args, **kw)
         return cls.instance
 
>>> class MyClass(object):
...     __metaclass__ = Singleton
monk ★★★★★
()
Ответ на: комментарий от monk

monk, ты чего? O_o

Во-первых, давай abstractable, как у ТС, а не singleton (нагляднее будет), во-вторых, тут метакласс (Singleton) не является экземпляром самого себя, что, вообще то, есть главный вопрос ТС.

naryl ★★★★★
()
Ответ на: комментарий от lovesan

AMOP и читаю, как единственный источник информации о дизайне MOP CLOS. Меня интересует не столько MOP CLOS сам по себе, сколько его дизайн и причины принятых решений. Common Lisp бесполезен внутри Scheme, где я пытаюсь написать аналог CLOS вместе с MOP в качестве упражнения на окончательное докуривание AMOP.

Рекурсия затем, что она понадобится, если с помощью MOP захочется забутстрепить свой CLOS с синглтоном и приватными слотами. Тогда было бы логичным, чтобы новый blackjack-and-hookers-class при наследовении от standard-class повторял бы его во всём. Но быть своим же суперклассом и наследоваться от своего же экземпляра blackjack-and-hookers-object ему не дано.

Я пытаюсь понять, это намеренно принятое решение («Такой хернёй никто не будет заниматься, поэтому механизмов в MOP для этого не будет») или такой вариант просто не предусмотрели (ведь экземпляры blackjack-and-hookers-class будут вести себя именно так, как задумано; только сам blackjack-and-hookers-class останется белой вороной). Если всё же не забыли, то какой бы механизм для этого существовал.

ilammy ★★★
() автор топика
(defclass c (standard-class) ())

(defclass c (standard-class) () (:metaclass c))

> (class-of (find-class 'c))
#<STANDARD-CLASS C>
monk ★★★★★
()
Ответ на: комментарий от monk

Пиши рабочий пример. Разбираться пока некогда:

Invalid initialization arguments:
  :DIRECT-DEFAULT-INITARGS, SB-PCL::SAFE-P, :DEFINITION-SOURCE,
  :DIRECT-SLOTS, :DIRECT-SUPERCLASSES, :NAME
in call for class #<STANDARD-CLASS SINGLETON-CLASS>.
   [Condition of type SB-PCL::INITARG-ERROR]

Restarts:
 0: [RETRY] Retry SLIME interactive evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [ABORT] Abort thread (#<THREAD "worker" RUNNING {100CF6E743}>)

Backtrace:
  0: (SB-PCL::CHECK-MI-INITARGS #<STANDARD-CLASS SINGLETON-CLASS> (:NAME MY-SINGLETON-CLASS :DIRECT-SUPERCLASSES NIL :DIRECT-SLOTS NIL :DEFINITION-SOURCE #S(SB-C:DEFINITION-SOURCE-LOCATION :NAMESTRING NIL ..
  1: ((:METHOD MAKE-INSTANCE (CLASS)) #<STANDARD-CLASS SINGLETON-CLASS> #<unavailable &REST argument>) [fast-method]
  2: ((FLET SB-THREAD::WITH-RECURSIVE-LOCK-THUNK :IN SB-MOP:ENSURE-CLASS-USING-CLASS))
  3: ((FLET #:WITHOUT-INTERRUPTS-BODY-564 :IN SB-THREAD::CALL-WITH-RECURSIVE-LOCK))
  4: (SB-THREAD::CALL-WITH-RECURSIVE-LOCK #<CLOSURE (FLET SB-THREAD::WITH-RECURSIVE-LOCK-THUNK :IN SB-MOP:ENSURE-CLASS-USING-CLASS) {82FFFEC9B}> #<SB-THREAD:MUTEX "World Lock" owner: #<SB-THREAD:THREAD "wo..
  5: ((:METHOD SB-MOP:ENSURE-CLASS-USING-CLASS (NULL T)) NIL MY-SINGLETON-CLASS :METACLASS SINGLETON-CLASS :DIRECT-SUPERCLASSES NIL :DIRECT-SLOTS NIL :DEFINITION-SOURCE #S(SB-C:DEFINITION-SOURCE-LOCATION :..
  6: ((FLET #:WITHOUT-INTERRUPTS-BODY-564 :IN SB-THREAD::CALL-WITH-RECURSIVE-LOCK))
  7: (SB-THREAD::CALL-WITH-RECURSIVE-LOCK #<CLOSURE (FLET SB-THREAD::WITH-RECURSIVE-LOCK-THUNK :IN SB-MOP:ENSURE-CLASS) {82FFFEEFB}> #<SB-THREAD:MUTEX "World Lock" owner: #<SB-THREAD:THREAD "worker" RUNNIN..
  8: (SB-MOP:ENSURE-CLASS MY-SINGLETON-CLASS :METACLASS SINGLETON-CLASS :DIRECT-SUPERCLASSES NIL :DIRECT-SLOTS NIL :DEFINITION-SOURCE #S(SB-C:DEFINITION-SOURCE-LOCATION :NAMESTRING NIL :TOPLEVEL-FORM-NUMBE..
  9: (SB-PCL::REAL-LOAD-DEFCLASS MY-SINGLETON-CLASS SINGLETON-CLASS NIL NIL (:DIRECT-DEFAULT-INITARGS NIL) NIL NIL NIL #S(SB-C:DEFINITION-SOURCE-LOCATION :NAMESTRING NIL :TOPLEVEL-FORM-NUMBER 0 :PLIST NIL)..
 10: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LET NIL (SB-PCL::LOAD-DEFCLASS (QUOTE MY-SINGLETON-CLASS) (QUOTE SINGLETON-CLASS) (QUOTE NIL) (LIST) (LIST :DIRECT-DEFAULT-INITARGS NIL) (QUOTE NIL) (QUOTE NIL) (QUOTE N..
 11: (SB-INT:SIMPLE-EVAL-IN-LEXENV (EVAL-WHEN (:LOAD-TOPLEVEL :EXECUTE) (LET NIL (SB-PCL::LOAD-DEFCLASS (QUOTE MY-SINGLETON-CLASS) (QUOTE SINGLETON-CLASS) (QUOTE NIL) (LIST) (LIST :DIRECT-DEFAULT-INITARGS ..
 12: (SB-INT:SIMPLE-EVAL-IN-LEXENV (DEFCLASS MY-SINGLETON-CLASS NIL NIL (:METACLASS SINGLETON-CLASS)) #<NULL-LEXENV>)
 13: (EVAL (DEFCLASS MY-SINGLETON-CLASS NIL NIL (:METACLASS SINGLETON-CLASS)))
 14: ((LAMBDA NIL :IN SWANK:INTERACTIVE-EVAL))
 --more--
anonymous
()
Ответ на: комментарий от anonymous

Мопед не мой. Копипаста с http://ru.wikipedia.org/wiki/Одиночка_(шаблон_проектирования)#.D0.9F.D1.80.D0...

Впрочем, правильный вариант:

(asdf:oos 'asdf:load-op :closer-mop)
(in-package :closer-mop)

(defclass singleton-class (standard-class) ;;метакласс, реализующий механизм синглтона
  ((instance :initform nil)))

(defmethod validate-superclass ((class singleton-class) (superclass standard-class))
  t) ;;Разрешаем наследование классов-синглтонов от обычных классов

(defmethod validate-superclass ((class singleton-class) (superclass singleton-class))
  t) ;;Разрешаем наследование классов-синглтонов от других классов-синглтонов

(defmethod validate-superclass ((class standard-class) (superclass singleton-class))
  nil) ;;Запрещаем наследование обычных классов от синглтонов

(defmethod make-instance ((class singleton-class) &key &allow-other-keys)
  (with-slots (instance) class
    (or instance (setf instance (call-next-method)))))

(defclass my-singleton-class ()
  ()
  (:metaclass singleton-class))

monk ★★★★★
()
Ответ на: комментарий от monk

SBCL, например, ругается, что на этот код, что на change-class в лоб.

И я тут ещё чуть внимательнее почитал AMOP:

Generic Function ensure-class-using-class

Syntax:
ensure-class-using-class class name &key direct-default-initargs direct-slots direct-superclasses name metaclass &allow-other-keys

Purpose:
This generic function is called to define or modify the definition of a named class.
<...>
If the class of the class argument is not the same as the class specified by the :metaclass argument, an error is signaled.

Initialization of Class Metaobjects
<...> Portable programs must not call change-class to change the class of any class metaobject or to turn a non-class object into a class metaobject.

То есть похоже, что это намеренное решение.

ilammy ★★★
() автор топика
Ответ на: комментарий от monk

Паразительно! Интересно, найду ли я когда-нибудь применение этому в реале.

Кстати, даже в CLHS нет validate-superclass. Это и есть часть этого MOP, и этого нет в стандарте?

anonymous
()
Ответ на: комментарий от anonymous

Интересно, найду ли я когда-нибудь применение этому в реале

Очень удобно для всякого рода ORM, proxy-слотов и прочего «снаружи выглядящего как классы» хлама.

Это и есть часть этого MOP, и этого нет в стандарте

Именно поэтому (asdf:oos 'asdf:load-op :closer-mop). Там полное соответствие MOP и внятные ошибки, если в конкретной реализации CL какой-то кусок MOP невозможно реализовать.

monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.