2

I'd like to control the way the values are saved in slots and what is returned when I read a slot. Here is my class definition:

(defclass object ()
  ((name :accessor name-access
         :initform 'noname
         :initarg :name)
   (value :accessor value-access
      :initform 10
      :initarg :value)))

I create the object this way:

(setf obj1 (make-instance 'object))

This is the way how I get the value of the slot name:

(name-access obj1)

And how I set a new value:

(setf (name-access obj1) 'realname)

What is the right way to override this accessor function (or method) in order to be able to make some changes to the object (on write) and to control the returned value?

Thank you.

andrei-n
  • 87
  • 2
  • 5

2 Answers2

5

You can just manually define the methods for getting and setting the slots:

(defclass foo ()
  ((name :initform 'noname
         :initarg :name)))

(defgeneric name-access (foo)
  (:method ((foo foo))
    (format t "~&Getting name.~%")
    (slot-value foo 'name)))

(defgeneric (setf name-access) (name foo)
  (:method (name (foo foo))
    (format t "~&Setting a new name.~%")
    (setf (slot-value foo 'name) name)))

(defparameter *foo* (make-instance 'foo))
(name-access *foo*)
; Getting name.
;=> NONAME

(setf (name-access *foo*) 'some-name)
; Setting a new name.
;=> SOME-NAME

(name-access *foo*)
; Getting name.
;=> SOME-NAME

The book Practical Common Lisp goes through these in chapter 17. You should read that.

jkiiski
  • 8,206
  • 2
  • 28
  • 44
5

You can extend the accessor methods defined by DEFCLASS:

CL-USER 66 > (defclass object ()
               ((name :accessor name-access
                      :initform 'noname
                      :initarg :name)
                (value :accessor value-access
                       :initform 10
                       :initarg :value)))
#<STANDARD-CLASS OBJECT 4220014953>

Writing, using a :before method:

CL-USER 67 > (defmethod (setf name-access) :before (new-value (o1 object))
               (print "hi"))
#<STANDARD-METHOD (SETF NAME-ACCESS) (:BEFORE) (T OBJECT) 40202283BB>

Reading, with an :around method:

CL-USER 68 > (defmethod name-access :around ((o1 object))
               (let ((name (call-next-method)))
                 (values name (length (symbol-name name)))))
#<STANDARD-METHOD NAME-ACCESS (:AROUND) (OBJECT) 4020061213>

Example:

CL-USER 69 > (let ((o1 (make-instance 'object)))
               (setf (name-access o1) 'foobar)
               (name-access o1))

"hi"     ; side effect
FOOBAR   ; return value 1
6        ; return value 2
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Thanks. It seems that in some communities it's considered a better way than redefining the accessor. But is it possible to modify the returned value with `:before` or `:around` methods? – andrei-n Apr 19 '16 at 16:34
  • @andrei-n : see the example. The around method returns whatever it wants. The before method has no influence. – Rainer Joswig Apr 19 '16 at 16:40
  • @andrei-n Changing the return value in an around-method has the danger of causing problems for subclasses later on. A primary method can always be overriden when different behaviour is needed, but there's no easy way of getting rid of an around-method. Personally I'd say that before/around/after -methods are good when you have to add extra functionality to an existing method, but defining a primary method should be preferred for core functionality. – jkiiski Apr 19 '16 at 17:16