Как специализировать обобщенную функцию для подклассов данного класса - PullRequest
3 голосов
/ 03 марта 2012

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

(defclass a () ())
(defclass b (a) ())
(defclass c (b) ())
(defclass d () ())

(defgeneric fun (param))
(defmethod fun ((param (<subclass of> a)))
  (format t "~a is a subclass of A~%" param))

(fun 'c) ;-> "C is a subclass of A"
(fun 'd) ;-> Error: not found method for generic function call (fun 'd)

Возможна ли такая отправка с CLOS? И если это так, что я должен написать вместо " подкласс "?

Ответы [ 2 ]

5 голосов
/ 03 марта 2012

Обратите внимание, что Common Lisp имеет функцию SUBTYPEP:

CL-USER 15 > (subtypep 'd 'a)
NIL
T

CL-USER 16 > (subtypep 'c 'a)
T
T

См. Документацию SUBTYPEP для значения двух возвращаемых значений (сначала говорится, что это подтип).Классы также являются типами.

Это означает, что ваша функциональность просто так:

(defun fun (class-name)
  (if (subtypep class-name 'a)
      (format t "~a is a subclass of A~%" class-name)
    (error "wtf")))

Помните: наследование в методе работает над наследованием класса.Это означает, что для использования наследования необходимо передать экземпляр определенного класса:

(defmethod fun ((param a))
  (format t "~a is a subclass of A~%" (class-name (class-of param))))

Выше приведен экземпляр класса A.

Вызовите его:

CL-USER 29 > (fun (make-instance 'a))
A is a subclass of A
NIL

CL-USER 30 > (fun (make-instance 'c))
C is a subclass of A
NIL

CL-USER 31 > (fun (make-instance 'd))

Error: No applicable methods for #<STANDARD-GENERIC-FUNCTION FUN 418001813C>
with args (#<D 40200011E3>)
  1 (continue) Call #<STANDARD-GENERIC-FUNCTION FUN 418001813C> again
  2 (abort) Return to level 0.
  3 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

CL-USER 32 : 1 > 

Существует способ упростить ^ h ^ h ^ h ^ h ^ h ^ h ^ h ^ h, чтобы было проще вызывать: вы можете убедиться, что класс завершен, используя что-то вроде CLOS:FINALIZE-INHERITANCE и использованиепрототип класса в качестве ввода (вызов CLASS-PROTOTYPE).Таким образом, вам не нужно создавать экземпляры класса для отправки.Можно было бы просто использовать экземпляр прототипа.

Альтернативная, уродливая версия, заключалась бы в жестком кодировании значений:

(defmethod fun0 ((param (eql 'b)))
  T)

(defmethod fun0 ((param (eql 'c)))
  T)
2 голосов
/ 03 марта 2012

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

Прежде чем продолжить, я думаю, что некоторые краткие замечания по терминологии важны.

Глоссарий Common Lisp HyperSpec определяет «подкласс» следующим образом:

класс, который наследуется от другого класса, называемого суперклассом. (Нет класс является подклассом самого себя.)

Это определение, хотя и интуитивно понятное, кажется мне странным, так как я ожидаю, что это будет определение "правильного подкласса" Однако все классы являются типами, и он определяет «подтип» как:

тип, принадлежность к которому совпадает или является надлежащим подмножеством членства другого типа, называемого супертипом. (Каждый тип является подтипом самого себя.)

Обратите внимание на круглые скобки: «Каждый тип является подтипом самого себя».

Также определяет «правильный подтип»:

(типа) подтип типа, который не совпадает с типом (т. Е. Его элементы являются "правильным подмножеством" типа).

Итак, в вашем примере B и C являются подклассами A, а также подтипами. С другой стороны, B, C, и A являются подтипами A.

В defmethod добавляется «имя специалиста параметров» . Это может быть символ, класс (который немного сложно набрать) или список, начинающийся с eql. Если вы предоставляете символ, он указывает класс, названный этим символом (который, конечно, является типом). Список eql указывает тип, состоящий из объектов, которые соответствуют объекту в списке.

Метод будет соответствовать любому объекту, который является членом типа, указанного специализатором. И, конечно же, член подтипа X также является членом X.

Итак, ваша первая проблема в том, что вы передаете символьные объекты в ваш метод; каждый символ имеет тип SYMBOL. Символ, который случается с именем класса, ничем не отличается в этом отношении; единственное отношение к классу состоит в том, что это имя класса, которое не является отношением подтипа.

Существуют объекты класса (возвращаемые find-class), но они не лучше, чем символы для специализации метода, потому что тип объекта класса обычно совпадает с типом объектов класса его подклассов.

Итак, вы остались использовать экземпляры или читать AMOP , чтобы научиться создавать собственные типы универсальных функций.

Если у вас есть экземпляр, вы можете написать метод следующим образом:

(defmethod fun ((param a))
  (if (eq (type-of param) 'a)
    (call-next-method)
    (format t "~a is a subclass of A~%" (type-of param))))

Если у вас есть простой способ получить экземпляры ваших классов, вы можете написать эту обертку:

(defmethod fun ((param symbol))
  (fun (retrieve-instance param)))

Тогда вы сможете передавать символы в шутку и получать желаемые результаты.

Если вы хотите использовать функции AMOP (которые не были определены стандартом, но широко доступны, см. Closer Project ), вы можете определить retrieve-instance следующим образом:

(defun retrieve-instance (name)
  (let ((class (find-class name)))
    (unless (class-finalized-p class)
      (finalize-inheritance class))
    (class-prototype class)))

Обратите внимание, что диспетчеризация метода - единственное, для чего хорош результат class-prototype; не пытайтесь изменить это или что-то в этом роде.

...