Re: equal-getf
2014-08-25 8:22 GMT+01:00 Erik Ronström:
> All right, I'm starting to understand how define-setf-expander works (I think).
That's the spirit!
> This is how the code has progressed so far. I renamed the function, added a test parameter, and also a version of remf.
>
> Erik
>
>
> (defun agetf (plist indicator &key (test #'equal) default)
> (loop for key in plist by #'cddr
> for value in (rest plist) by #'cddr
> when (funcall test key indicator)
> return value)
> default)
Very well, but the keyword arguments will add a little bit of
complexity in the setf expander. One way to avoid it is by using
optional arguments, but that introduces a style problem when you want
to provide a later argument but not an earlier one.
> (define-setf-expander agetf (place indicator &key (test #'equal) &environment env)
> (multiple-value-bind (temps vals stores store-form access-form)
> (get-setf-expansion place env) ; Get setf expansion for place
> (let ((i-temp (gensym))
> (store (gensym))
> (s-temp (first stores)))
> (if (cdr stores) (error "Can't expand this."))
A better test would be (or (null stores) (rest stores)), otherwise
you'll let go a no-values places, and binding ,s-temp will signal an
error stating you can't bind to nil.
> ;;; Return the setf expansion as five values.
> (values (append temps (list i-temp)) ; Temporary variables
> (append vals (list indicator)) ; Value forms
Here's the complexity of the keyword arguments: they too should all be
evaluated once only from left-to-right.
There is an easy way, providing a temp for the form (list
,@rest-args), where rest-args is the &rest argument, and in the writer
and reader forms, use destructuring-bind to obtain the keyword
arguments.
This will take care of evaluating repeated keyword arguments (e.g.
:test 'eql :test 'equal), even though the first is what counts, and
it'll take care of evaluating all forms in case :allow-other-keys is
true (e.g. :test 'eql :foo (max 1 most-positive-fixnum)
:allow-other-keys t).
If you care about maximum efficiency, the expansion will suffer a bit
more in added complexity, much like half of what destructuring-bind
does, but to save the resulting code from accessing the keyword
arguments repeatedly.
I suggest you just go for the simple way here, because I think the
performance and garbage impact will be negligible for 99.999% of the
times.
> (list store) ; Store variables
> (let ((key-cons (gensym "key-cons"))
> (gaccess-form (gensym "access-form"))
> (gtest (gensym "test")))
> `(let ((,gaccess-form ,access-form)
> (,gtest ,test))
Here, you'd do:
(destructuring-bind (&key ((,gtest :test) 'eql) default)
,rest-temp
...
> (loop for ,key-cons on ,access-form by #'cddr do
> (when (funcall ,gtest (car ,key-cons) ,i-temp)
> (setf (cadr ,key-cons) ,store)
> (return ,store))
> finally
> (let ((,s-temp (list* ,i-temp ,store ,access-form)))
> ,store-form
> (return ,store))))) ; Storing form
> `(agetf ,access-form ,i-temp :test ,test) ; Accessing form
Here, due to keyword arguments, you'd:
(apply 'agetf ,access-form ,i-temp ,rest-temp)
> ))))
>
> (defmacro aremf (place indicator &key (test #'equal))
> (let ((gplace (gensym))
> (gindicator (gensym))
> (key-cons (gensym))
> (gtest (gensym)))
> `(let ((,gplace ,place)
> (,gindicator ,indicator)
> (,gtest ,test))
> (if (funcall ,gtest (car ,gplace) ,gindicator)
> (progn
> (setf ,place (cddr ,gplace))
This isn't a setf expander. However, you need to use
get-setf-expansion too. With the current code, you're evaluating the
subforms of place more than once.
Another thing to care about is the actual evaluation of the
reader-form. If I remember correctly, read-modify-write macros
evaluate all arguments from left-to-right. For the (each, in case of
multiple) place argument, only its subforms are evaluated. After
this:
1. The reader-form of (each) place is evaluated (from left-to-right)
2. The modification computation is performed (for each place from
left-to-right), and its return values are bound to the store-vars (of
each place)
4. The writer-form of (each) place is evaluated (from left-to-right)
http://www.lispworks.com/documentation/HyperSpec/Body/05_aaa.htm
http://www.lispworks.com/documentation/HyperSpec/Body/05_ac.htm
PS: I wrote something about this topic, but I moved it to the end of
the message, because I think it would confuse rather than help by
leaving it here.
> t)
> (loop for ,key-cons on (cdr ,gplace) by #'cddr do
> (when (funcall ,gtest (cadr ,key-cons) ,gindicator)
Just before getting to the end of the list, ,gtest will be called with
a first argument of nil, so you need to check for (cdr ,key-cons)
before.
> (setf (cdr ,key-cons) (cdddr ,key-cons))
> (return t))
> finally
> (return nil))))))
You're almost there, good job.
Implementing the setf of agetf and the aremf macro are really good
exercises about defining and using setf expansions, and a great step
for understanding what happens under the hood.
Addenda:
The notable multiple place macros are setf, psetf, shiftf, rotatef and
assert. There rules for assert are explicitly undefined.
Surprisingly, only psetf and rotatef aren't violations to the
read-modify-write macro rules, except for dealing with multiple values
places.
For setf:
1. For each pair of arguments (from left-to-right):
1.1. The subforms of place are evaluated and bound
1.2. The newvalue form is evaluated, and its values are bound to the
place's store-vars
1.3. The writer-form of place is evaluated (by the rules, this would
happen after point 1, as in psetf)
(setf a b c d) => (progn (setf a b) (setf c d))
For psetf:
1. For each pair of arguments (from left-to-right):
1.1. The subforms of place are evaluated and bound
1.2. The newvalue form is evaluated, and its return values are bound
to the place's store-vars
2. For each pair (from left-to-right):
2.1. The writer-form of place is evaluated
3. Return nil
For shiftf:
1. For each place (from left-to-right):
1.1. The subforms of place are evaluated and bound
2. The reader-form of the first place is evaluated, and its return
values are saved
3. For each of the rest of the places (from left-to-right):
3.1. The reader-form of place is evaluated, and its return values are
bound to the store-vars of the previous place
4. The newvalue form is evaluated, and its return values are bound to
the store-vars of the last place (by the rules, this would happen
before point 3)
5. For each place (from left-to-right)
5.1. The writer-form of place is evaluated
6. The values returned by the first reader-form are returned (e.g.
point 2 could just be a multiple-value-prog1)
For rotatef:
1. For each place (from left-to-right):
1.1. The subforms of place are evaluated and bound
2. The reader-form of the first place is evaluated, and its return
values are bound to the store-vars of the last place
3. For each of the rest of the places (from left-to-right):
3.1. The reader-form of place is evaluated, and its return values are
bound to the store-vars of the previous place
4. For each place:
4.1. The writer-form of place is evaluated
5. Return nil
Best regards,
Paulo Madeira
_______________________________________________
Lisp Hug - the mailing list for LispWorks users
lisp-hug@lispworks.com
http://www.lispworks.com/support/lisp-hug.html