Name: Anonymous 2010-08-27 18:56
apparently one of you was in /v/ a day ago going on a little tirade against c++. why would it be so popular if there was "absolutely nothing good about it" like you said?
;;; Define the classes, A, B inheriting from A, C inheriting from A,
;;; D inheriting from B and C (or in the latter example, from C and B)
(defclass a () ())
(defclass b (a) ())
(defclass c (a) ())
(defclass d (b c) ())
;;; Define 3 methods on each class
(defmethod meth ((x a)) (print "From A"))
(defmethod meth ((x b)) (print "From B"))
(defmethod meth ((x c)) (print "From C"))
;; Show how the diamond inheritance works by default in CL
;; using standard method combination with no qualifiers
;; or other significant specializers.
;; The order of superclasses is what is used: (b c)
(meth (make-instance 'd)) ; => "From B"
;; Reverse the order and observe new results:
(defclass d (c b) ())
(meth (make-instance 'd)) ; => "From C"
;; what if we want to perform something in `meth' that applies to everything
;; inheriting from A?
(defmethod meth :before ((x a))
(print "Before A"))
(meth (make-instance 'd)) ; =>
; "Before A"
; "From B"
(meth (make-instance 'c)) ; =>
; "Before A"
; "From C"
(meth (make-instance 'b)) ; =>
; "Before A"
; "From B"
(meth (make-instance 'a)) ; =>
; "Before A"
; "From A"
;;; There are also :after and :around qualifiers for the standard method
;;; combination, which offer you a lot more control
;;; :around allows fully overriding the method, while
;;; :after allows you to run some code after it (like :before)
;;; see the Hyperspec on the exact details on the order of methods
;;; Since we're doing this in a runtime system, we'll remove the :before
;;; method at runtime (you can of course just unintern the class or use
;;; the inspector to do it for you, or just reload everything):
(remove-method #'meth (find-method #'meth '(:before) `(,(find-class 'a))))
(meth (make-instance 'a)) ;=> "From A"
;; a standard way to chain methods using the standard method combination
(defmethod meth ((x b))
(print "From B")
(call-next-method))
(defmethod meth ((x c))
(print "From C")
(call-next-method))
(meth (make-instance 'd)) ; => "From B" "From C" "From A"
;; reversing the order the effective method is chosen by using an alternate method combination
(defgeneric meth2 (x)
(:method-combination or :most-specific-last))
(defmethod meth2 or ((x a)) (print "From A"))
(defmethod meth2 or ((x b)) (print "From B"))
(defmethod meth2 or ((x c)) (print "From C"))
(meth2 (make-instance 'd)) ;=> "From A"
;; a simple, but very violent/destructive way to to call A's
;; method directly (convert the class to a base class):
(defmethod meth ((x d))
(meth (change-class x 'a)))
(meth (make-instance 'd)) ; => "From A"
;; a proper way to directly call the method specialized on A,
;; bypassing other "next" methods using the MOP:
(require :closer-mop)
(defmethod meth ((x d))
(funcall (c2mop:method-function
(find-method #'meth '() (list (find-class 'a))))
(list x) '()))
(meth (make-instance 'd)) ; => "From A"
;; What if we want some crazy arbitrary, runtime determined way of picking
;; which method to run? Here's an example which picks
;; a random method to run at runtime!
(define-method-combination :random ()
((methods *))
`(ecase (random ,(length methods))
,@(loop for i from 0
for method in methods
collect `(,i (call-method ,method ',(remove method methods))))))
(defgeneric meth3 (x)
(:method-combination :random))
(defmethod meth3 :random ((x a)) (print "From A"))
(defmethod meth3 :random ((x b)) (print "From B"))
(defmethod meth3 :random ((x c)) (print "From C"))
(meth3 (make-instance 'd))
;=> prints "From A", "From B" or "From C", depending on the current random state
;;; Either way I could go on showing standard CL, MOP, method combination,
;;; specializer tricks and many others which show that you have complete
;;; liberty in choosing any method you want despite multiple inheritance,
;;; however such tricks are countless and you could easily see them
;;; by reading the AMOP and a good CL book, so I won't bother showing
;;; any more here.
;;; However, I tend to find the defaults sane, and I can't remember resorting
;;; to most of the tricks I presented here in my day to day coding, but
;;; the point is that these features are there in case you need them
;;; and not only that, you can pretty much redefine the OO system
;;; in itself in almost its entirety using the MOP, if you so need.