How to execute a function on setf place

447 Views Asked by At

I have a list which contains some symbols and values. The goal is to setf the class slot with the accessor, whose symbol is provided by the list :

(defclass my-class ()
 ((attr :accessor attr)))

(let ((to-call '(attr "some-value"))
      (obj (make-instance 'my-class)))
 (setf `(,(car to-call) obj) (cadr to-call)))

I have tried via a macro :

(defmacro call-accessor (to-call)
 `(setf (,(car to-call) obj) "some-value"))

(let ((to-call '(attr "some-value"))
      (obj (make-instance 'my-class)))
 (call-accessor to-call))

Which fails too, since to-call is a symbol and not a list.

  • eval does not work since to-call is a lexical variable;
  • It is not possible to do a let over the macro to give it the list;
  • I have tried with with-slots and with-accessors but the problem remains the same, because they are macros too.
  • I have considered macros which declares other macro, and symbol-macrolet too.

How can I setf a slot via an accessor corresponding to a symbol in my list ?

Thank you.

3

There are 3 best solutions below

0
On BEST ANSWER

Calling the accessor function

The goal is to setf the class slot with the accessor

An accessor is a pair of functions. You can get the part which sets the value via FDEFINITION. The name of the function is a list (SETF accessor-name ). This is unusual: Common Lisp has in this case function names which are not symbols, but lists.

CL-USER 14 > (let ((to-call '(attr "some-value"))
                   (obj (make-instance 'my-class)))
               (funcall (fdefinition `(setf ,(first to-call)))
                        (second to-call)
                        obj)
               (describe obj))

#<MY-CLASS 40200614FB> is a MY-CLASS
ATTR      "some-value"

Using a function call-accessor:

CL-USER 25 > (let ((to-call '(attr "some-value"))
                   (obj (make-instance 'my-class)))
               (flet ((call-accessor (obj to-call)
                        (funcall (fdefinition `(setf ,(first to-call)))
                                 (second to-call)
                                 obj)))
                 (call-accessor obj to-call)
                 (describe obj)))

#<MY-CLASS 402000220B> is a MY-CLASS
ATTR      "some-value"

using SETF with APPLY to hide the FDEFINITION call

To use setf with a computed accessor, one might need to use an apply form and a custom function.

Something like call-accessor would naturally be a function, because it does runtime lookup and takes values. Trying to use a macro would be more useful if the accessor would be known at compile time.

CL-USER 23 > (let ((to-call '(attr "some-value"))
                   (obj (make-instance 'my-class)))
               (flet (((setf my-setter) (new-value object accessor) 
                        (funcall (fdefinition `(setf ,accessor))
                                 new-value
                                 obj)))
                 (flet ((call-accessor (obj to-call)
                          (setf (apply #'my-setter obj (list (first to-call)))
                                (second to-call))))
                   (call-accessor obj to-call)
                   (describe obj))))

#<MY-CLASS 40200009AB> is a MY-CLASS
ATTR      "some-value"

Choice of data structure

I think it's okay to compute accessor functions and similar. There may be use cases for that. CLOS was designed to be dynamic and reflective to allow these things.

2
On

You can use setf of slot-value, using the slot name symbol in your data pair:

(let ((to-call '(attr "some-value")))
  (setf (slot-value obj (first to-call)) (second to-call)))

However, using slot-value directly is usually only sensible when you are wrangling at object internals (like initialize-instance methods/combinations; or maybe you are working on some serialization mechanism).

If that is not the case, you are using the object just as an associative data structure. I'd suggest using an alist, plist, or hash-map instead.

0
On

I agree that an alternative structure like a hash-table should probably be used. But to extend Svante's answer regarding slot-value, the working code is simply:

(setf (slot-value obj 'attr) "some-value")