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

initialization protocol



Here is my proposed initialization protocol.  It is based on the
one I sent out before.  Like that protocol, the primary goals are
simplicity and flexibility.  This proposal also includes some comments
about the conventions for use of this protocol.

There are two major changes in this protocol:

  1.) there is a system-supplied initialize method which processes
      the &rest argument to make-instance as plist of slot names
      and values.  This method initializes the slots of the instance
      from the values in this plist, any slot for which no value
      is specified in the plist is initialized from the default
      value specified in the defclass form.  This change provides 
      the user with access to the canned functionality of treating
      the &rest argument to make-instance as a plist of slot-names
      and values.  It provides it in a way that Moon seems to like
      in which the :initforms in the defclass are only evaluated
      when no value for the slot is specified in the &rest argument
      to make-instance.

  2.) initialize take the &rest argument which was passed to
      make-instance as a single argument rather than an &rest or
      &key argument.  This is the part of this protocol I am the
      least attached to.  If we want to change this so that
      make-instance uses apply to call initialize I am amenable
      to that.  The reason I have done it this way is to avoid the
      pain of always having to add:
        (.. &rest options &key &allow-other-keys)
      to initialize methods.

Here is some prototype code which implements this protocol.  I am
using this code here because it is simple, and it makes it exactly
clear what happens when.

(defmethod make-instance ((class-name symbol) &rest options)
  (apply #'make-instance (class-named class-name) options))

(defmethod make-instance ((class class) &rest options)
  (let ((instance (make-instance-internal class)))
    (initialize instance options)
    instance))

(defmethod initialize ((o object) options)
  (dolist (slot-name (all-slots (class-of o)))
    (setf (slot-value o slot-name)
          (let* ((getf-default (list nil))
                 (plist-value (getf options slot-name getf-default)))
            (if (eq plist-value getf-default)
                (evaluate-initform o slot-name)
                plist-value)))))



The convention for the &rest argument to make-instance (which I will
call the init-plist but someone else is free to invent a better term) is
as follows:
  
  It should be a true plist, the keys (odd-numbered elements of
  the list) should be either:

     slot-names, in which case the default initialize method (or
     some user-defined initialize method will initialize the
     slots from those values

     keywords, a keyword implies that the initialize method is
     going to do more "work" than just set the slot value (with
     slot value).  Things specified with keywords are often
     compound values or values which don't map directly onto
     slot values.

Here are some sample initialize methods.  These show some standard stuff
people might want to do:

(defmethod initialize ((p plane) options)
  (when options (error "Planes accept no init-plist")))


(defmethod initialize ((b boat) options)
  (call-next-method)                   ;first do slot values
  (when (cadr (memq options ':start))  ;then start if we should
     (start boat)))


;;; note that the flet with apply used in this example is the downside
;;; of the decision to pass the options to initialize as one argument.
;;; as I said above, I think this is worth it.
(defmethod initialize ((m method) options)
  ;; We do hairy processing to set function specializers and
  ;; qualifiers in parallel.  So we accept those as keywords.
  (flet ((internal (&key function specializers qualifiers)
            (setf (slot-value m 'function) function
                  (slot-value m 'specializers) specializers
                  (slot-value m 'qualifiers) qualifiers)
            .. code to process the new values in parallel ..))
     (apply #'internal options)))

;;; In this case, we want to process the options if there are any,
;;; but before we do so, we want to evaluate ALL the :initforms in
;;; the defclass and install them in the instance.
(defmethod initialize ((s ship) options)
  (if (null options)
      (call-next-method)
      (progn
         (initialize s ())  ;Go evaluate :initforms.
         .. now process options ..)))

Some bugs (and answers) with my proposal:

  B: The convention of using flet and apply is weird.
  A: Its no so bad really, and its much better than having to say
     &rest options &key &allow-other-keys all over

  B: The default initialize method only signals an error if the
     init-plist has an even number of elements.  If the user
     wants to implement complete error-checking of the init-plist
     they have to do it themselves.
  A: Right.  Doing complete error checking of the init-plist
     involves checking the values too, so if the user is going
     to do that they will have no more code to write to check
     the keys.

Please, speak now...