Search code examples
objectclonecommon-lispclos

Is there a generic method for cloning CLOS objects?


I'm looking for a way to clone CLOS objects in a shallow manner, so the created object would be of the same type with the same values in each slot, but a new instance. The closest thing I found is a standard function copy-structure which does this for structures.


Solution

  • There is no standard predefined way to copy CLOS objects in general. It is not trivial, if possible at all, to provide a reasonable default copy operation that does the right thing (at least) most of the time for arbitrary objects, since the correct semantics change from class to class and from application to application. The extended possibilities the MOP provides make it even harder to provide such a default. Also, in CL, being a garbage collected language, copying of objects is not really needed very often, e.g. when passed as parameters or being returned. So, implementing your copy operations as needed would probably be the cleanest solution.

    That being said, here is what I found in one of my snippet files, which might do what you want:

    (defun shallow-copy-object (original)
      (let* ((class (class-of original))
             (copy (allocate-instance class)))
        (dolist (slot (mapcar #'slot-definition-name (class-slots class)))
          (when (slot-boundp original slot)
            (setf (slot-value copy slot)
                  (slot-value original slot))))
        copy))
    

    You will need some MOP support for class-slots and slot-definition-name.

    (I probably adopted this from an old c.l.l thread, but I can't remember. I never really needed something like this, so it's utterly untested.)

    You can use it like this (tested with CCL):

    CL-USER> (defclass foo ()
               ((x :accessor x :initarg :x)
                (y :accessor y :initarg :y)))
    #<STANDARD-CLASS FOO>
    CL-USER> (defmethod print-object ((obj foo) stream)
               (print-unreadable-object (obj stream :identity t :type t)
                 (format stream ":x ~a :y ~a" (x obj) (y obj))))
    #<STANDARD-METHOD PRINT-OBJECT (FOO T)>
    CL-USER> (defparameter *f* (make-instance 'foo :x 1 :y 2))
    *F*
    CL-USER> *f*
    #<FOO :x 1 :y 2 #xC7E5156>
    CL-USER> (shallow-copy-object *f*)
    #<FOO :x 1 :y 2 #xC850306>