[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Initialize-instance



>>Date: Mon, 6 Jul 92 10:38:38 -0400
>>From: jbk@world.std.com (Jeffrey B Kane)
>>To: info-mcl@cambridge.apple.com
>>Subject: Initialize-instance
>>
>>I have another couple of quick CLOS related questions.  The first has to do
>>with defining my own "initialize-instance" method for my classes. For my
>>simplest example, I didn't need any parameters, so I tried:
>>
>>(defclass twindow (window)
>>  ((garbage :initarg :garbage     ; holds some value that I might need
>>            :initform nil
>>            :accessor garbage)))
>>
>>(defmethod initialize-instance ((me twindow) &rest)
>>  (print "Hi, I'm initializing an instance of a twindow")
>>  (initialize-instance (coerce me window)))
>>
>Forget the coerce command, that's not what you want at all.
>Here's two different ways to do this:
>
>; This doesn't shadow the previous definition, it just runs BEFORE it.
>; Also check out AFTER methods.
>(defmethod initialize-instance :before ((me twindow) &rest rest)
>  (declare (ignore rest))
>  (print "Hi, I'm initializing an instance of a twindow"))
>
>; This wraps AROUND the prevous definition. You can provide 
>; different args to CALL-NEXT-METHOD.
>(defmethod initialize-instance :around ((me twindow) &rest rest)
>  (declare (ignore rest))
>  (print "Hi, I'm about to initialize an instance of a twindow")
>  (call-next-method)    
>  (print "Hi, I just initialized an instance of a twindow"))
>   
>

This is a matter of taste, but I rarely use :AROUND methods. I save them
for cases where I want to add a patch to some existing code. Arguably,
patches are better done with ADVISE, but I try to save :AROUND methods
for patches anyway. I would code Steve's second example as:

(defmethod initialize-instance ((me twindow) &rest rest)
  (declare (ignore rest))
  (print "Hi, I'm about to initialize an instance of a twindow")
  (call-next-method)    
  (print "Hi, I just initialized an instance of a twindow"))

Sometimes you DO want an :AROUND method.

1) You want to get control around all :BEFORE, primary, & :AROUND methods.
   An :AROUND method can decide not to CALL-NEXT-METHOD, eliding execution
   of the :BEFORE & :AFTER methods. If a primary method does not CALL-NEXT-METHOD,
   the :BEFORE & :AFTER methods will run (the :BEFORE methods run before the
   primaries).

2) :BEFORE & :AFTER methods get the arguments that were passed by the inner-most
   :AROUND method. Hence, if supply changed arguments to CALL-NEXT-METHOD from a
    primary method, the :AFTER methods will see the original, not the changed
    args:

? (defmethod primary ((x integer))
    x)
#<STANDARD-METHOD PRIMARY (INTEGER)>
? (defmethod primary :before ((x integer))
    (print `(:before ,x)))
#<STANDARD-METHOD PRIMARY :BEFORE (INTEGER)>
? (defmethod primary :after ((x integer))
    (print `(:after ,x)))
#<STANDARD-METHOD PRIMARY :AFTER (INTEGER)>
? (defmethod primary ((x fixnum))
    (print `(primary ,x))
    (call-next-method (1+ x)))
#<STANDARD-METHOD PRIMARY (FIXNUM)>
? (primary 1)

(:BEFORE 1) 
(PRIMARY 1) 
(:AFTER 1) 
2
? (defmethod around ((x integer))
    x)
#<STANDARD-METHOD AROUND (INTEGER)>
? (defmethod around :before ((x integer))
    (print `(:before ,x)))
#<STANDARD-METHOD AROUND :BEFORE (INTEGER)>
? (defmethod around :after ((x integer))
    (print `(:after ,x)))
#<STANDARD-METHOD AROUND :AFTER (INTEGER)>
? (defmethod around :around ((x integer))
    (print `(:around ,x))
    (call-next-method (1+ x)))
#<STANDARD-METHOD AROUND :AROUND (INTEGER)>
? (around 1)

(:AROUND 1) 
(:BEFORE 2) 
(:AFTER 2) 
2
? 

Finally, there's a canonical use of CALL-NEXT-METHOD from inside of an
INITIALIZE-INSTANCE method that happens often enough that it's worth
mentioning. Sometimes you want to change a few args to the usual
INITIALIZE-INSTANCE method, then do a little more work. I often find
myself doing this when I am initializing a custom window. I want the
Macintosh WindowRecord to be created, but I don't want the window to
be shown on the screen until everything is done:

(defclass my-window (window) ())

(defmethod initialize-instance ((w my-window) &rest rest &key (window-show t))
  (apply #'call-next-method
         w                              ; the window
         :window-show nil               ; don't show it yet
         rest)                          ; include other initargs
  (do-hairy-initialization w)
  (if window-show
    ; Show the window unless my caller said not to
    (window-show w)))


Note that the :WINDOW-SHOW keyword may appear twice in the argument list given
to the next method. Common Lisp is defined to look at only the first occurrence
of each keyword given to a function that accepts keyword arguments.

Clear as mud, right?