From: Peter Van Eynde
Subject: CLOS and the effects on 'eq'
Date: 
Message-ID: <6xg0iqmckr.fsf@lant.be>
Hello people,

I'm using allegro 5.0.1 on Solaris and I'm having and "interesting"
time with CLOS.

I have an object, stored in the global variable *A* that I need to
turn into another object (yes I know the problems, it just needs to
take over all the slots of another object). To do this I use this
little function: 

;; slightly edited to remove cruft.

(defun suck-the-life-force-out-of (table other-table)
  ;; change the class:
  (change-class table
                (class-of other-table))

  (let* ((new-slots (clos:compute-slots (class-of other-table)))
         (new-slot-names 
          (loop for slot in new-slots
              collect (clos:slot-definition-name slot)))
         
         (old-slots (clos:compute-slots (class-of table)))
         (old-slot-names 
          (loop for slot in old-slots
              collect (clos:slot-definition-name slot))))
    
    ;; destroy the slots not bound in the new object:
    (loop for component in old-slot-names
        do
          (cond
           ;; it exists
           ((and (member component new-slot-names)
                 ;; and it not bound:
                 (not 
                  (slot-boundp other-table component)))
            (slot-makunbound table
                             component))
           ;; it doesn't exist?
           ((not (member component new-slot-names))
            (error "Cannot happen: ~S in ~S doesn't exist in ~S"
                   component
                   table
                   other-table))))

    ;; copy the slots over _AND DESTROY THEM_
    (loop for component in new-slot-names
        when (slot-boundp other-table component)
        do
          (setf (slot-value table component)
            (slot-value other-table component))
          (slot-makunbound other-table 
                           component)))
  table)

This seem to work. table turns into a copy of other-table. _Except_
that there is a slot that contains the table object inside table. Now
of course it contains a reference to the empty shell that once was
other-table. To fix this I refill the slot with correct information:

;;; first transform the object
(suck-the-life-force-out-of file new-file)

;; show information
(format t "~&The slot is: ~S -> ~S ~%"
        (slot-value file 'nc)
        (slot-value (slot-value file 'nc) 'c))

;; rename and 
(setf (slot-value
       (slot-value file 'nc)
       'name)
  (intern (format nil "THE-GOOD-~A"
                  (slot-value
                   (slot-value file 'nc)
                   'name))
          *the-kt-package*))

;; change the value
(setf (slot-value
       (slot-value file 'nc)
       'c)
  (lambda ()
    (foo file)))

(format t "~&NOW The slot is: ~S -> ~S ~%"
        (slot-value file 'nc)
        (slot-value (slot-value file 'nc) 'c))

This produces the good output of: (slightly edited)

The slot is: #<WRITE-BACK-LRU-CACHE Name: STRING-INDEX-PAIR-CACHE @
               #x8e23d32> 
     -> #<Closure (:INTERNAL OPEN-STRING-INDEX-TABLE 1) @ #x8e5a46a> 
NOW The slot is: #<WRITE-BACK-LRU-CACHE Name: 
                    THE-GOOD-STRING-INDEX-PAIR-CACHE @ #x8e23d32> 
    -> #<Closure (:INTERNAL (:INTERNAL (METHOD REGENERATE-TABLE #) 0)
                   1) @ #x8fc671a> 

Everything looks great. Now the problems start. *A* is the global
variable that contains the object 'table', but I get:

(format t "~&NOW2 The slot is: ~S -> ~S ~%"
                      (slot-value file 'nc)
                      (slot-value (slot-value file 'nc) 'c))
gives: 
NOW2 The slot is: #<WRITE-BACK-LRU-CACHE Name: 
                    THE-GOOD-STRING-INDEX-PAIR-CACHE @
                    #x8e23d32> 
    -> #<Closure (:INTERNAL (:INTERNAL (METHOD REGENERATE-TABLE #) 0)
                  1) @ #x8fc671a> 
Still ok.

(format t "~&*A* The slot is: ~S -> ~S ~%"
        (slot-value *A* 'nc)
        (slot-value (slot-value *A* 'nc) 'c))
gives:
*A* The slot is: #<WRITE-BACK-LRU-CACHE Name: STRING-INDEX-PAIR-CACHE @
                   #x8f2543a> 
     -> #<Closure (:INTERNAL OPEN-STRING-INDEX-TABLE 1) @ #x8f28afa> 
This is wrong. This is the old value, not the current one. Maybe
file and *A* got out of sync?

(format t "~&*A* and file are eq? ~S eql? ~S equalp? ~S~%"
        (eq *A* file)
        (eql *A* file)
        (equalp *A* file))
gives:
*A* and file are eq? T eql? T equalp? T

Huh? *A* and file are EQ but the value of a certain slot is different?
Stranger still I do:

(setf *B* file)
(format t "~&file The slot is: ~S -> ~S ~%"
        (slot-value file 'nc)
        (slot-value (slot-value file 'nc) 'c))
(format t "~&*A* The slot is: ~S -> ~S ~%"
        (slot-value *A* 'nc)
        (slot-value (slot-value *A* 'nc) 'c))
(format t "~&*B* The slot is: ~S -> ~S ~%"
        (slot-value *B* 'nc)
        (slot-value (slot-value *B* 'nc) 'callback))
And get:
file The slot is: #<WRITE-BACK-LRU-CACHE Name: 
                    THE-GOOD-STRING-INDEX-PAIR-CACHE @
                    #x8e23d32> 
    -> #<Closure (:INTERNAL (:INTERNAL (METHOD REGENERATE-TABLE #) 0)
                   1) @ #x8fc671a> 
*A* The slot is: #<WRITE-BACK-LRU-CACHE Name: STRING-INDEX-PAIR-CACHE @
                   #x8f2543a> 
    -> #<Closure (:INTERNAL OPEN-STRING-INDEX-TABLE 1) @ #x8f28afa> 
*B* The slot is: #<WRITE-BACK-LRU-CACHE Name: STRING-INDEX-PAIR-CACHE @
                   #x8f2543a> 
   -> #<Closure (:INTERNAL OPEN-STRING-INDEX-TABLE 1) @ #x8f28afa> 

So if I even assign table to *B* the value of the slot is wrong again?

I think somehow a clos cache got out of sync, but I fail to find a
reference to this in the documentation. Any hints?

Groetjes, Peter

-- 
LANT nv/sa, Research Park Haasrode, Interleuvenlaan 15H, B-3001 Leuven
·····················@lant.be                       Phone: ++32 16 405140
http://www.lant.be/                                 Fax: ++32 16 404961

From: Peter Van Eynde
Subject: Re: CLOS and the effects on 'eq'
Date: 
Message-ID: <6xr92ako2r.fsf@lant.be>
Erik Naggum <····@naggum.net> writes:

> * Peter Van Eynde <·····@lant.be>
> | I have an object, stored in the global variable *A* that I need to turn
> | into another object (yes I know the problems, it just needs to take over
> | all the slots of another object).
> 
>   But why do you need this?  If object identity is what you're after, it is
>   a frightfully excessive solution to a problem that could be accomplished
>   with a simple container class that "forwarded" accesses to the contained
>   object, or even a cons cell.  If you don't need object identity, I fail
>   to see what you want.  Could you explain?

Uffa. A difficult question. I think the answer is I'm lazy :-).

This program makes use of a pretty complex hierachy of objects, most
of them talking the same protocol (ie. they are subclasses of a
certain class). Part of that protocol is a "regenerate" option that
should cause the object to do a 'gc'-like operation that should remove 
excess space (the program is strongly optimized for reading, when
creating or replacing data it will waste space as a design choice. At
a later moment the regenerate message is given and the object should
clean up).

The easiest way to clean up is to create a new object with the same
parameters and just stuff the current data into it. Then you'll have
an old object containing the excess data and a new object having only
the useful data. Then I just wanted to steal the data structures
created by the newer object and give them to the older object. This
way the hierarchy still works (object identity is preserved) and I've
avoided writing dedicated cleanup code.

In the past I would have a list of the slots that I needed to steal
hard-coded, after messing a bit with the MOP I wanted to use that to
look up all the relevant slots and move them over. This seems to work
in another case and is also used to automagicly propage messages to
slots that are a subclass of the protocol class. All of this works, so 
I was pretty shocked to find this problem, and I wanted to learn if I
was doing something wrong.

Oh: that it is slow is no problem as the clean-up should only be 
performed off-line. 

Thanks, Peter

-- 
LANT nv/sa, Research Park Haasrode, Interleuvenlaan 15H, B-3001 Leuven
·····················@lant.be                       Phone: ++32 16 405140
http://www.lant.be/                                 Fax: ++32 16 404961
From: Peter Van Eynde
Subject: SOLVED: Re: CLOS and the effects on 'eq'
Date: 
Message-ID: <6xg0ilqfxk.fsf@lant.be>
Hi people,

Following a very useful reply from Franz tech-support I've found the
problem. Pilot error I fear ;-).

It seems that change-class is pretty ill-defined in the presence of an
optimizing compiler (see the NOTE in the change-class entry of the
hyperspec), and that to be certain you shouldn't use the slot-value's
of the changed object anymore in that method. Putting it into a helper 
function that is called after the change-class seems to resolve this
problem.

I've recorded this problem in the Cliki
(http://ww.telent.net/cliki/index).

Groetjes, Peter

-- 
LANT nv/sa, Research Park Haasrode, Interleuvenlaan 15H, B-3001 Leuven
·····················@lant.be                       Phone: ++32 16 405140
http://www.lant.be/                                 Fax: ++32 16 404961