I have a simple task:
Given a class, I need a function that can add elements to one of the
fields of the class.
It's easy to do if the function knows the name of the field, but what
if the function doesn't know it and we need to pass it as an input
parameter to the function.
Let's say
(defclass test1 ()
((fld1 :accessor test1-fld1 :initarg :fld1 :type list)))
(setq o (make-instance 'test1 :fld1 nil))
(defun append-element (o el)
(with-slots (fld1) o
(setf fld1 (append fld1 (list el)))))
How to modify the append-element function so that it doesn't know
about the fld1 and had semantics of (append-element o el field-name)
so we can call it like (append-element o 'a 'fld1)?
I am sure, it can be done as a macro
(defmacro append-element (o el field)
`(with-slots (,field) o
(setf ,field (append ,field (list ,el)))))
But I'd like to know if it's possible to pass some kind of setf-able
place to the function?
> (defclass test1 ()
> ((fld1 :accessor test1-fld1 :initarg :fld1 :type list)))
>
> (setq o (make-instance 'test1 :fld1 nil))
> (defun append-element (o el)
> (with-slots (fld1) o
> (setf fld1 (append fld1 (list el)))))
>
> How to modify the append-element function so that it doesn't know
> about the fld1 and had semantics of (append-element o el field-name)
> so we can call it like (append-element o 'a 'fld1)?
Not entirely sure I understand, but maybe the SLOT-VALUE accessor is
what you're looking for?
(defun append-element (object element slot)
(setf (slot-value object slot)
(append (slot-value object slot)
(list element))))
--
Frode V. Fjeld
Whenever I see this form in a macro:
(setf ,x (stuff ,x))
I think it is wrong due to multiple evaluation (from "On Lisp").
However, lots of people keep reiterating it in posts here.
Are there well-known situations where it isn't wrong?
-pete
> How to modify the append-element function so that it doesn't know
> about the fld1 and had semantics of (append-element o el field-name)
> so we can call it like (append-element o 'a 'fld1)?
You can use SLOT-VALUE if you are happy to set the slot directly. If
you want to use the accessor (which you probably should want to do)
then I think that you can use FDEFINITION, based on the accessor's name:
(defun set-field (o accessor new)
(funcall (fdefinition `(setf ,accessor)) new o))
--tim
Thank you, Tim, very good example.
(defun append-element (o el getter setter)
(funcall setter (append (funcall getter o) (list el)) o)
)
(append-element o :new-el #'test1-fld1 #'(setf test1-fld1))
Norbert
> You might try #'(setf foo) to get the writer part of the accessor #'foo.
> I tried it, but I don't know if this is always possible.
This works, but it only works if you know the name FOO: you can't say
(let ((fun 'foo) #'(setf fun)) for instance: you need FDEFINITION for
that.
Did you mean
(defun append-element (o el accessor)
(let ((getter (fdefinition accessor))
(setter (fdefinition `(setf ,accessor))))
(funcall setter (append (funcall getter o) (list el)) o)))
?
something like that
I searched the HS a lot but couldn'd find any guarantee that
an accessor foo always has an (fdefinition `(setf ,foo)),
neither could I find the contrary.
> Given a class, I need a function that can add elements to one of the
> fields of the class.
> It's easy to do if the function knows the name of the field, but what
> if the function doesn't know it and we need to pass it as an input
> parameter to the function.
> Let's say
> (defclass test1 ()
> ((fld1 :accessor test1-fld1 :initarg :fld1 :type list)))
> (setq o (make-instance 'test1 :fld1 nil))
> (defun append-element (o el)
> (with-slots (fld1) o
> (setf fld1 (append fld1 (list el)))))
If I wanted to stick with a function-based (as opposed to macro-based)
solution, I would do it like this:
(defun append-element (object element getter setter)
(let ((current (funcall getter object)))
(funcall setter (append fldl (list element)) object)))
This will be pretty easy to use with your class as written. You use
(append-element o 'foo #'test-fld1 #'(setf test-fld1))
It's also a fairly general solution. Consider:
(defparameter *table* (make-hash-table))
(append-element *table* 'baz
(lambda (ht) (gethash 'bar ht))
(lambda (val ht) (setf (gethash 'bar ht) val))
You can build more convenient functions on top of this one, like
(append-element-to-slot (object element slot)
(append-element object element
(lambda (o) (slot-value o slot))
(lambda (v o) (setf (slot-value o slot))))
(append-element-to-table-value (table element key)
(append-element table element
(lambda (ht) (gethash key ht))
(lambda (v ht) (setf (gethash key ht) v)))
I doubt it will ever be as syntactically clean as a macro-based
solution, but it's hard to beat its ease of implementation.
Cheers,
Pillsy
Indeed not. It's not because you can write (setf (foo x) v) that you
can recover a (function (setf foo)) or a (fdefinition '(setf foo)).
SETF can implement the "standard" place in its own way, without having
a (setf foo) function defined.
The correct way to "reify" a random place in general, is to wrap it in
a closure, or a pair of closures.
See for example:
http://groups.google.com/group/comp.lang.lisp/msg/1799d5db9267c523
which is wrong, since it doesn't use get-setf-expansion to avoid
duplicate evaluation of the arguments to the place, but gives you the
closure side (it answered on a question on variables, not places).
So a full solution would be:
;;; Locatives with multiple-value places.
(defmacro & (place &environment env)
(multiple-value-bind (vars vals store-vars writer-form reader-form)
(get-setf-expansion place env)
`(let* (,@(mapcar (function list) vars vals)
,@store-vars)
(lambda (m &rest values)
(ecase m
((set)
(psetf ,@(loop :for v :in store-vars :nconc (list v '(pop values))))
,writer-form)
((get)
,reader-form))))))
(defun deref (locative) (funcall locative 'get))
;; Notice that using (defun (setf deref) ...) would prevent to store
;; multiple values, if the place accepted them. So we must use
;; defsetf with a macro setter.
(defmacro set-deref (locative value-expression)
`(multiple-value-call ,locative 'set ,value-expression))
(defsetf deref set-deref)
;; So that:
(let* ((a 1) (b 2) (c 3) (l (& (values a b c))))
(print (multiple-value-list (deref l)))
(setf (deref l) (values 4 5 6))
(list a b c))
prints: (1 2 3)
returns: (4 5 6)
;; and
(let* ((a 1)
(b 2)
(c 3)
(ls (vector (& (values a b c))
(& a)
(& b)
(& c)))
(i -1))
(print (multiple-value-list (deref (aref ls (incf i)))))
(print (multiple-value-list (deref (aref ls (incf i)))))
(decf i)
(incf (deref (aref ls (incf i))) 10)
(list (list i) a b c))
prints: (1 2 3)
(1)
returns: ((1) 11 2 3)
--
__Pascal Bourguignon__
http://www.informatimago.com
> ;;; Locatives with multiple-value places.
>
> (defmacro & (place &environment env)
> (multiple-value-bind (vars vals store-vars writer-form reader-form)
> (get-setf-expansion place env)
> `(let* (,@(mapcar (function list) vars vals)
> ,@store-vars)
> (lambda (m &rest values)
> (ecase m
> ((set)
> (psetf ,@(loop :for v :in store-vars :nconc (list
> v '(pop values)))) ,writer-form)
> ((get)
> ,reader-form))))))
>
>
> (defun deref (locative) (funcall locative 'get))
>
> ;; Notice that using (defun (setf deref) ...) would prevent to store ;;
> multiple values, if the place accepted them. So we must use ;; defsetf
> with a macro setter.
>
> (defmacro set-deref (locative value-expression)
> `(multiple-value-call ,locative 'set ,value-expression))
>
> (defsetf deref set-deref)
This is really neat, thanks.
Tamas
> Does that always work?
>
> I searched the HS a lot but couldn'd find any guarantee that
> an accessor foo always has an (fdefinition `(setf ,foo)),
> neither could I find the contrary.
It pretty much has to work for ordinary CLOS classes (by "ordinary" I
mean "assuming no one has done too much MOP stuff"): If I can define a
method on (SETF FOO) then I can use FDEFINITION on it.