From: Javier
Subject: A CLOS question: slot-value and setf
Date: 
Message-ID: <1162388270.014909.261730@e3g2000cwe.googlegroups.com>
I've got a problem for a class. Whenever I access (for reading) any of
it slots, I'd like a variable in the instantiated object to be
automatically updated. This is the class:

(defclass objeto ()
  ((nombre :initarg :nombre :initform (gensym ""))
   (fuerza :initform 0
	   :initarg :fuerza)
   (ultimo-acceso :initform (get-universal-time)
		  :accessor ultimo-acceso)))

for example, whenever I want to read fuerza I'd like it to update
ultimo-acceso, so the method should be:

(defmethod fuerza ((obj objeto))
  (setf (ultimo-acceso obj) (get-universal-time))
  (slot-value obj 'fuerza))

and does work. The problem here is when I want to do the same thing
when writing:

(defmethod (setf fuerza) (obj cuanto)
  (setf (ultimo-acceso obj) (get-universal-time))
  (setf (slot-value obj 'fuerza) cuanto))

CL-USER> (defparameter a (make-instance 'objeto))
A
CL-USER> (setf (fuerza a) 8)
There is no applicable method for the generic function
  #<STANDARD-GENERIC-FUNCTION (SETF ULTIMO-ACCESO) (1)>
when called with arguments
  (3371376009 8).
   [Condition of type SIMPLE-ERROR]

With this example, may I understand that setf cannot be assigned using
methods?
Elsewhere, if I try to assign it using defun:

CL-USER> (fmakunbound '(setf fuerza))
(SETF FUERZA)
CL-USER> (defun (setf fuerza) (obj cuanto)
  (setf (slot-value obj 'ultimo-acceso) (get-universal-time))
  (setf (slot-value obj 'fuerza) cuanto))

I get the same error.
It seems that methods conflicts in some way with setf definition.

I think that a possible solution might be shadowing slot-value, but I
think this is too much work for doing this simple thing to just one of
my classes. Another thing might be using MOP, but then I'm lose.
I hope somebody could give some ideas.

From: Christophe Rhodes
Subject: Re: A CLOS question: slot-value and setf
Date: 
Message-ID: <sqzmbbp9bf.fsf@cantab.net>
"Javier" <·······@gmail.com> writes:

> I hope somebody could give some ideas.

Look up the order of arguments to setf functions.

Christophe
From: Pascal Costanza
Subject: Re: A CLOS question: slot-value and setf
Date: 
Message-ID: <4qrmqdFnt2spU1@individual.net>
Javier wrote:
> I've got a problem for a class. Whenever I access (for reading) any of
> it slots, I'd like a variable in the instantiated object to be
> automatically updated. This is the class:
> 
> (defclass objeto ()
>   ((nombre :initarg :nombre :initform (gensym ""))
>    (fuerza :initform 0
> 	   :initarg :fuerza)
>    (ultimo-acceso :initform (get-universal-time)
> 		  :accessor ultimo-acceso)))
> 
> for example, whenever I want to read fuerza I'd like it to update
> ultimo-acceso, so the method should be:
> 
> (defmethod fuerza ((obj objeto))
>   (setf (ultimo-acceso obj) (get-universal-time))
>   (slot-value obj 'fuerza))
> 
> and does work. 

Note that you could as well define fuerza as an accessor in the class 
objeto, and then write this:

(defmethod fuerza :before ((obj objecto))
   (setf (ultimo-acceso obj) (get-universal-time)))

> The problem here is when I want to do the same thing
> when writing:
> 
> (defmethod (setf fuerza) (obj cuanto)
>   (setf (ultimo-acceso obj) (get-universal-time))
>   (setf (slot-value obj 'fuerza) cuanto))

The problem here is that in setf functions, the new value to be set is 
passed as the first argument, not as the second. This is to allow setf 
functions to work in conjunction with arbitrary combinations of 
optional, rest and keyword parameters. So try this:

(defmethod (setf fuerza) :before (cuanto (obj objeto))
   (declare (ignore cuanto))
   (setf (ultimo-acceso obj) (get-universal-time)))

> I think that a possible solution might be shadowing slot-value, but I
> think this is too much work for doing this simple thing to just one of
> my classes. Another thing might be using MOP, but then I'm lose.
> I hope somebody could give some ideas.

You don't need the MOP for these things. Just define the parameters in 
the correct order. ;)


Pascal

-- 
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Javier
Subject: Re: A CLOS question: slot-value and setf
Date: 
Message-ID: <1162391181.719575.317390@i42g2000cwa.googlegroups.com>
Pascal Costanza wrote:
> Note that you could as well define fuerza as an accessor in the class
> objeto, and then write this:
>
> (defmethod fuerza :before ((obj objecto))
>    (setf (ultimo-acceso obj) (get-universal-time)))

Oh this is nice, I forget that :before exists. :)


> The problem here is that in setf functions, the new value to be set is
> passed as the first argument, not as the second. This is to allow setf
> functions to work in conjunction with arbitrary combinations of
> optional, rest and keyword parameters. So try this:
>
> (defmethod (setf fuerza) :before (cuanto (obj objeto))
>    (declare (ignore cuanto))
>    (setf (ultimo-acceso obj) (get-universal-time)))


Thanks, I was confused because setf works in the inverted order.

> > I think that a possible solution might be shadowing slot-value, but I
> > think this is too much work for doing this simple thing to just one of
> > my classes. Another thing might be using MOP, but then I'm lose.
> > I hope somebody could give some ideas.
>
> You don't need the MOP for these things. Just define the parameters in
> the correct order. ;)

Just another question... any idea on how to make this automatically to
happen for any slot of the class without having to manually program it?
And for classes descending from this one? (I think that I then need MOP
for this). I have tried this, but doesn't work (it seems that
slot-value is not implemented as a method):

CL-USER> (defmethod slot-value :before ((obj objeto) slot)
  (when (not (equal slot 'ultimo-acceso))
    (setf (ultimo-acceso obj) (get-universal-time))))

SLOT-VALUE already names an ordinary function or a macro.
   [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]
From: Pascal Costanza
Subject: Re: A CLOS question: slot-value and setf
Date: 
Message-ID: <4qrpteFo85daU1@individual.net>
Javier wrote:

> Just another question... any idea on how to make this automatically to
> happen for any slot of the class without having to manually program it?
> And for classes descending from this one? (I think that I then need MOP
> for this). I have tried this, but doesn't work (it seems that
> slot-value is not implemented as a method):
> 
> CL-USER> (defmethod slot-value :before ((obj objeto) slot)
>   (when (not (equal slot 'ultimo-acceso))
>     (setf (ultimo-acceso obj) (get-universal-time))))
> 
> SLOT-VALUE already names an ordinary function or a macro.
>    [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]

Indeed, slot-value is specified to be a function, not a generic function.

For this to work on all slots, you would need to specify your own 
metaclass and define methods on slot-value-using-class, and the likes. 
Roughly like this:

(defclass my-class (standard-class)
   ())

(defmethod validate-superclass (...) ...)

(defmethod slot-value-using-class :before
   ((class my-class) object slot)
   ...)

(defmethod (setf slot-value-using-class) :before
   (new-value (class my-class) object slot)
   ...)

(defmethod slot-boundp-using-class :before
   ((class my-class) object slot)
   ...)

(defmethod slot-makunbound-using-class :before
   ((class my-class) object slot)
   ...)

However, a tricky thing here is that this would trigger on accesses to 
'ultimo-acceso as well, which would lead to an infinite loop. You can 
avoid that by conditionalizing in the slot-xyz-using-class methods, or 
by using different slot definition metaclasses for the different slots 
and specializing the third parameters in those methods accordingly 
(which are not slot names but slot definition metaobjects).

However, I'd say this is probably overkill, unless you expect this to 
happen a lot.

If you indeed want to do this and want to ensure portable code, you 
could do worse than use the Closer to MOP compatibility layer, since 
different CLOS implementations deviate from the CLOS MOP spec to various 
degrees.


Pascal

-- 
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Javier
Subject: Re: A CLOS question: slot-value and setf
Date: 
Message-ID: <1162424260.434639.224570@e3g2000cwe.googlegroups.com>
Pascal Costanza wrote:
> Javier wrote:
>
> > Just another question... any idea on how to make this automatically to
> > happen for any slot of the class without having to manually program it?
> > And for classes descending from this one? (I think that I then need MOP
> > for this). I have tried this, but doesn't work (it seems that
> > slot-value is not implemented as a method):
> >
> > CL-USER> (defmethod slot-value :before ((obj objeto) slot)
> >   (when (not (equal slot 'ultimo-acceso))
> >     (setf (ultimo-acceso obj) (get-universal-time))))
> >
> > SLOT-VALUE already names an ordinary function or a macro.
> >    [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]
>
> Indeed, slot-value is specified to be a function, not a generic function.
>
> For this to work on all slots, you would need to specify your own
> metaclass and define methods on slot-value-using-class, and the likes.
> Roughly like this:
>
> (defclass my-class (standard-class)
>    ())
>
> (defmethod validate-superclass (...) ...)
>
> (defmethod slot-value-using-class :before
>    ((class my-class) object slot)
>    ...)
>
> (defmethod (setf slot-value-using-class) :before
>    (new-value (class my-class) object slot)
>    ...)
>
> (defmethod slot-boundp-using-class :before
>    ((class my-class) object slot)
>    ...)
>
> (defmethod slot-makunbound-using-class :before
>    ((class my-class) object slot)
>    ...)
>
> However, a tricky thing here is that this would trigger on accesses to
> 'ultimo-acceso as well, which would lead to an infinite loop. You can
> avoid that by conditionalizing in the slot-xyz-using-class methods, or
> by using different slot definition metaclasses for the different slots
> and specializing the third parameters in those methods accordingly
> (which are not slot names but slot definition metaobjects).
>
> However, I'd say this is probably overkill, unless you expect this to
> happen a lot.
>
> If you indeed want to do this and want to ensure portable code, you
> could do worse than use the Closer to MOP compatibility layer, since
> different CLOS implementations deviate from the CLOS MOP spec to various
> degrees.


Thanks for your ideas. I have to have a glance to Closer (but first
learn MOP better).
Meanwhile, I've written this little macro, which not only solves the
problem of having to write write and read methods for every slot, but
also avoids me to type somo repetitive code (like :initarg and
:accessor).

(defgeneric read-wraper (object slot))
(defmethod read-wraper (object slot) nil)
(defgeneric write-wraper (object slot))
(defmethod write-wraper (object slot) nil)
(defmacro defobjeto (name parents slots)
  (let ((complete-slots (mapcar
			 (lambda (slot)
			   (if (listp slot)
			       (cond
				 ((not (member :initarg slot))
				  (append slot `(:initarg ,(car slot))))
				 ((not (member :accessor slot))
				  (append slot `(:accessor ,(car slot))))
				 (t slot))
			       `(,slot :initarg ,slot
				       :accessor ,slot)))
			 slots)))
    `(progn
       (defclass ,name ,parents
	 ,complete-slots)
       ,@(mapcar (lambda (slot)
		   (let ((accessor (second (member :accessor slot))))
		     `(progn
			(defmethod ,accessor :before ((obj ,name))
			  (read-wraper obj ',(car slot)))
			(defmethod (setf ,accessor) :before (value (obj ,name))
			  (declare (ignore value))
			  (write-wraper obj ',(car slot))))))
		 complete-slots))))

It could be also possible to add some keywords, like for example
:private, so it does not generate a wrapper nor accessors.
Please give some suggestions if find some bug or bad style.
From: Javier
Subject: Re: A CLOS question: slot-value and setf
Date: 
Message-ID: <1162468457.241525.125980@f16g2000cwb.googlegroups.com>
Cleaned up:

(defgeneric read-wraper (object slot))
(defgeneric write-wraper (object slot))
(shadow 'defclass)
(defmacro defclass (name parents slots &body options)
  (let ((complete-slots
	 (mapcar (lambda (slot)
		   (cond

		     ((and (listp slot) (not (member :private slot)))
		      (when (not (member :initarg slot))
			(nconc slot `(:initarg ,(car slot))))
		      (when (not (member :accessor slot))
			(nconc slot `(:accessor ,(car slot))))
		      (when (not (member :initform slot))
			(nconc slot `(:initform nil)))
		      slot)

		     ((and (listp slot) (member :private slot))
		      (if (= (length slot) 2)
			  (car slot)
			  (remove :private slot)))

		     (t `(,slot :initarg ,slot
				:accessor ,slot
			 	:initform nil))))
		 slots)))
    `(progn
       ;defclass
       ,(if options
	    `(cl:defclass ,name ,parents
	       ,complete-slots
	       ,options)
	    `(cl:defclass ,name ,parents
	       ,complete-slots))
       ;defmethods for read-wraper and write-wraper
       ,@(mapcar (lambda (slot)
		   (let ((accessor (second (member :accessor slot))))
		     `(progn
			(defmethod ,accessor :before ((obj ,name))
			  (read-wraper obj ',(car slot)))
			(defmethod (setf ,accessor) :before (value (obj ,name))
			  (declare (ignore value))
			  (write-wraper obj ',(car slot))))))
		 (remove-if (lambda (l)
			      (if (listp l)
				  (not (member :accessor l))
				  t))
			    complete-slots)))))



-----------

An example of use:

(defclass objeto ()
  ((nombre :initform (gensym ""))
   (fuerza :initform 0)
   (ultimo-acceso :private)))

(defmethod read-wraper ((obj objeto) slot)
  (declare (ignore slot))
  (setf (slot-value obj 'ultimo-acceso) (get-universal-time)))
(defmethod write-wraper ((obj objeto) slot)
  (declare (ignore slot))
  (setf (slot-value obj 'ultimo-acceso) (get-universal-time)))

CL-USER> (defparameter obj (make-instance 'objeto))
OBJ
CL-USER> (nombre obj)
#:|1936|
CL-USER> (slot-value obj 'ultimo-acceso)
3371423223
CL-USER> (nombre obj)
#:|1936|
CL-USER> (slot-value obj 'ultimo-acceso)
3371423243

You can see how ultimo-acceso is modified every time a public read is
made, and how including the :private keyword do not create any accessor
nor a wraper.