Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I do setf on a struct's accessors when using intern

I'd like to setf different fields of a struct depending on a certain variable. I decided to use the following approach:

Generate a string with the field's accessor name:

(setq my-string (format nil "STRUCT-ESTADISTICAS-NUM-~S" x))

and then use intern with funcall:

(funcall (intern my-string) *estadisticas*)

This call returns the correct value of the struct's field, but if I try setf to modify this value it complains saying:

(setf (funcall(intern my-string) *estadisticas*) 0)
Error: `(SETF FUNCALL)' is not fbound

I can understand why it doesn't work, but I can't find a way to modify the struct's fields. Any idea? Thank you.

like image 406
aitorpazos Avatar asked Sep 04 '10 08:09

aitorpazos


2 Answers

You want to call a writer function of the struct via its name, and the name of the writer is the list (setf accessor-name); so

(funcall (fdefinition (list 'setf (intern my-string))) 0 estadisticas)

Edit:

Not seeing the rest of your code, it's hard to fathom what went wrong. On SBCL this works for me:

(defstruct point x y)
(let ((point (make-point :x 1 :y 2)))
  (funcall (fdefinition (list 'setf (intern "POINT-X"))) 10 point)
  point)

The above evaluates to

#S(POINT :X 10 :Y 2),

as expected.

like image 67
huaiyuan Avatar answered Nov 07 '22 10:11

huaiyuan


Motivation:

Structures are a relatively low-level facility. 'Speed' was an important design goal. Indirection via writer functions is not supported by the standard (as I read it). Today, use CLOS as the default, unless one needs better efficiency of structures (faster read and writes of slots with structures are sometimes possible).

First - style:

Don't use INTERN, use FIND-SYMBOL. Also specify the package, otherwise FIND-SYMBOL will use the runtime value of *package* as the package

Second - DEFSTRUCT

If I read the ANSI CL standard correctly, it is not that DEFSTRUCT creates writer functions for slots like DEFCLASS does.

CL-USER 24 > (defstruct foo bar baz)
FOO

CL-USER 25 > #'(setf foo-bar)

Error: Undefined function (SETF FOO-BAR) in form (FUNCTION (SETF FOO-BAR)).

So, constructing such a name (SETF FOO-BAR) and trying to find a function for that will fail, since there is no such function defined by the DEFSTRUCT.

That in user code (setf (foo-bar some-struct) 42) works, is based on defined SETF expansions provided by DEFSTRUCT, but not on defined SETF accessor functions.

Some Common Lisp implementations may provide writer functions as a non-standard extension to ANSI CL.

Possible solutions:

a) use CLOS classes, DEFCLASS does what you want

b) write the writer functions yourself

(defun (setf foo-bar) (new-value struct)
   (setf (foo-bar struct) new-value))

Now:

(funcall (fdefinition '(setf foo-bar)) 300 *foo*)

Above then works.

c) (SETF SLOT-VALUE) - another non-standard feature of some implementations.

In some implementations of Common Lisp this works not only for CLOS classes, but also for structures:

(setf (slot-value some-struct 'bar) 42)

I'm not sure if Allegro CL does support that, but that would be easy to find out.

like image 28
Rainer Joswig Avatar answered Nov 07 '22 10:11

Rainer Joswig