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

Indian givers, CLOS style

A suggestion concerning the status of lists returned from metaobject

From the current draft of the function pages (a-to-c):

> The generic function {\bf class-direct-subclasses} returns the set as a
> list.  A class can be removed from the set by calling {\bf
> remove-direct-subclass}.  The effect of calling {\bf
> add-direct-subclass} or {\bf remove-direct-subclass} on previously
> returned values of {\bf class-direct-subclasses} is unspecified.

The motivation for the last sentence is obvious---to make it possible to cut
down the amount of consing that needs to be done when direct subclass is
removed (additions pose no real problem).  Similar wording is found in several
other places where metaobject accessor functions return lists.

It's seems fine to tell the user that the result returned by the accessor
must not be smashed.  This permits the implementation to share structure with
that result if it so chooses.

It's seems fine to tell the user that the result returned by the accessor may
share with other things the user provided (e.g., class-direct-slots may share
with the :direct-slots initarg when the class what initialized).  This too
permits the implementation to share structure with the user-supplied source if
it so chooses.

It's seems fine to tell the user that the result returned by the accessor may
be a different list every time.  And if the list represents a set, maybe the
order the order will vary.  This permits the implementation to reconstruct the
list using whatever means it has at its disposal.

But that last sentence means that the user cannot trust that the list returned
by the accessor will remain intact; a safely-conscious user would have to copy
it before saving it away (or engaging in any computation that might end up
sharing with that list); your average user will think that they can get away
without copying it, and a small number of them will be saldy mistaken on that
account and will end up saddled with a subtle bug ("Gee. My CLOS program
seemed to be working until I reloaded the file.").  I believe this is out of
spirit for Common Lisp; I think it would make much more sense if the spec that
the implementation could not take back the answers it returned earlier.
Naturally, portable methods on MOP functions would be expected to live by
the same rules.

There are a few known places where the extra consing regularly gets out of
hand (e.g., the direct methods of class t).  It would be more in keeping
with the rest of the language if implementions were asked to find ways to
handle these problem cases within the normal framework.  E.g.,

(defclass class-with-lots-of-direct-methods (standard-class)
     ((internal-direct-methods :initform ())   ; mutable list
      (copiedp :initform nil)                  ; up to date copy exists 
      (exportable-direct-methods)))            ; copy (immutable) 

(defmethod specializer-direct-methods
           ((class class-with-lots-of-direct-methods)) 
  (unless (slot-value class 'copiedp)
     ;; Copy the internal list and save it
     (setf (slot-value class 'copiedp) t)
     (setf (slot-value class 'exportable-direct-methods)
           (copy-list (slot-value class 'internal-direct-methods))))
  (slot-value class 'exportable-direct-methods))

(defmethod add-direct-method ((class class-with-lots-of-direct-methods)
  (pushnew class (slot-value class 'internal-direct-methods))
  (when (slot-value class 'copiedp)
    (pushnew class (slot-value class 'exportable-direct-methods)))

(defmethod remove-direct-method ((class class-with-lots-of-direct-methods)) 
  (setf (slot-value class 'internal-direct-methods)
        (delete method (slot-value class 'internal-direct-methods)))
  (setf (slot-value class 'copiedp) nil)

Of course, this is not the only way the performance tradeoffs could be made.
But it should be clear that the implementor does have considerable room to
manoeuvre without an "Indian giver" provision.