(defmacro place-let (bindings &body body &environment env)
"Similar to LET*, but the names in BINDINGS are bound in such a way
that SETF/SETQ will work on them."
(if bindings
(let ((accessor (gensym "ACCESS"))
(value (gensym "VALUE")))
(multiple-value-bind (dummies vals new setter getter)
(get-setf-expansion (cadar bindings) env)
`(let* (,@(mapcar #'list dummies vals)
(,(car new) ,getter))
(flet ((,accessor ()
,(car new))
((setf ,accessor) (,value)
(setf ,(car new) ,value)
,setter
,value))
(symbol-macrolet ((,(caar bindings) (,accessor)))
(setq ,(car new) ,getter)
(place-let ,(cdr bindings) ,@body))))))
`(progn ,@body)))
;;; As an example, asif can be written using place-let.
(defmacro asif (test then &optional else)
`(place-let ((it ,test))
(if it ,then ,else)))
First question I have, does anything like this already exist in a
library? I couldn't come up with a better name, but perhaps someone
else can.
Second, I'm curious what might be wrong with it. The compiled lisps
I've tried (sbcl and ccl) inline the flet functions and the generated
code is reasonable. Clisp does not, but I'm not sure it makes as much
difference there.
I do find it curious that ccl fails when trying to macroexpand uses of
place-let. The error is that :function (setf #:ACCESSnnn) is not a
symbol. The failure only happens inside of slime, and the problem
appears to only be with CCL:MACROEXPAND-ALL. Is this a bug I should
report in Clozure?
Thanks,
David
#+clisp ext:letf is close, but doesn't seem to do exactly the same.
It's designed to bind symbol-macros such as those defined by clisp to
customize the coding systems.
In any case, LETF and LETF* would be nice names, similar to SETF, INCF,
etc.
> Second, I'm curious what might be wrong with it. The compiled lisps
> I've tried (sbcl and ccl) inline the flet functions and the generated
> code is reasonable. Clisp does not, but I'm not sure it makes as much
> difference there.
> I do find it curious that ccl fails when trying to macroexpand uses of
> place-let. The error is that :function (setf #:ACCESSnnn) is not a
> symbol. The failure only happens inside of slime, and the problem
> appears to only be with CCL:MACROEXPAND-ALL. Is this a bug I should
> report in Clozure?
It would be a bug, clhs clearly specifies that (setf name) functions can
be defined with flet and labels.
--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
> In any case, LETF and LETF* would be nice names, similar to SETF, INCF,
> etc.
LETF was, I think, used by Symbolics CL (maybe Zetalisp but I think it
was available from the CL system as well) to do something different and
(if I remember rightly) evil.
What it did (again, if I remember correctly) was to "bind" a form to a
value. So for instance if I have something like this:
(let ((secret-place (cons nil nil)))
(defun x () (cdr secret-place))
(defun (setf x) (new) (setf (cdr secret-place) new)))
Then you could say
(letf (((x) 3))
;; now (x) is 3, and the old value
... (x) ... (setf (x) ...) ...)
;; now the old value of (x) is restored
That sounds kind of cool (and it was kind of cool) until you try and
think how it might interact with a multithreaded system (I mean a
system which actually has multiple concurrent threads, not the
traditional emulation on top of a single thread).
I thought at first that place-let did the same thing, but it's actually
much less evil.
The following is not very different:
(defmacro asif (test then &optional else)
`(let ((it ,test))
(if it ,then ,else)))
So what does place-let buy you on top?
Pascal
--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
ContextL provides dletf, which is like the Symbolics LETF, but
thread-safe. Description and examples are here:
http://p-cos.net/documents/special-full.pdf
> On 12/01/2011 18:17, David Brown wrote:
>>
>> (defmacro asif (test then&optional else)
>> `(place-let ((it ,test))
>> (if it ,then ,else)))
>
> The following is not very different:
>
> (defmacro asif (test then &optional else)
> `(let ((it ,test))
> (if it ,then ,else)))
>
> So what does place-let buy you on top?
Well, the above asif doesn't allow (setf it value) to change the place
described by test.
Even the anaphora package asif evaluates the inner parts of the test
each time they are referenced in the else.
(place-let ((element (gethash (compute-expensive-key) (find-the-hash-table))))
(or element
(let ((new-element (compute-element)))
(setf element new-element)
element)))
The expensive key lookup and finding the hash table are only done once.
Whereas with the anaphora asif, they will be computed three times each.
David
Are you sure this does what you think it does?
(defun test ()
(let ((table (make-hash-table)))
(place-let ((foo (gethash 1 table)))
(setf foo 2)
(format t "before: ~S~%" (gethash 1 table))
(clrhash table)
(format t "after: ~S~%" (gethash 1 table))
(format t "foo: ~S~%" foo))))
> (test)
before: 2
after: NIL
foo: 2
An idiom like this:
(or (gethash key table)
(setf (gethash key table) new-value))
...should normally have no serious performance implications, because
good hashtable implementations should cache the result of looking up the
internal place for a subsequent invocation of either gethash or (setf
gethash).
> ContextL provides dletf, which is like the Symbolics LETF, but
> thread-safe. Description and examples are here:
> http://p-cos.net/documents/special-full.pdf
That looks interesting. The trick of using a level of indirection is
quite clever.
> On 12/01/2011 20:31, David Brown wrote:
>> The expensive key lookup and finding the hash table are only done once.
>> Whereas with the anaphora asif, they will be computed three times each.
>
> Are you sure this does what you think it does?
>
> (defun test ()
> (let ((table (make-hash-table)))
> (place-let ((foo (gethash 1 table)))
> (setf foo 2)
> (format t "before: ~S~%" (gethash 1 table))
> (clrhash table)
> (format t "after: ~S~%" (gethash 1 table))
> (format t "foo: ~S~%" foo))))
>
> > (test)
> before: 2
> after: NIL
> foo: 2
Well, it does what I thought it should do. Whether that is always
meaningful is a different question.
> An idiom like this:
>
> (or (gethash key table)
> (setf (gethash key table) new-value))
>
> ...should normally have no serious performance implications, because
> good hashtable implementations should cache the result of looking up
> the internal place for a subsequent invocation of either gethash or
> (setf gethash).
In my case, both 'key' and 'table' were expensive to get.
David
I'm not criticizing you for doing something that's useful for your use
case. I'm just trying to express my doubts that this generalizes well.
Without further investigation, it should better not become part of a
generally available library.
> On 12/01/2011 20:31, David Brown wrote:
>> On Wed, Jan 12 2011, Pascal Costanza wrote:
> Are you sure this does what you think it does?
>
> (defun test ()
> (let ((table (make-hash-table)))
> (place-let ((foo (gethash 1 table)))
> (setf foo 2)
> (format t "before: ~S~%" (gethash 1 table))
> (clrhash table)
> (format t "after: ~S~%" (gethash 1 table))
> (format t "foo: ~S~%" foo))))
>
> > (test)
> before: 2
> after: NIL
> foo: 2
Ok, this is helpful. I agree that it is much clearer if each access to
a name gets the value, and that this is unlikely to be a performance
issue with hash tables. Here's a version that works this way (it gives
foo: NIL with the above test). I've also renamed it to place-let* since
it matches let*.
(defmacro place-let* (bindings &body body &environment env)
"PLACE-LET* ({(name place)}*) declaration* form*
Evaluates FORM in a lexical context where each NAME will be bound such
that a reference to NAME will be treated as a reader of the PLACE,
and (SETF NAME) will write to the PLACE."
(if bindings
(let ((accessor (gensym "ACCESS"))
(value (gensym "VALUE")))
(multiple-value-bind (dummies vals new setter getter)
(get-setf-expansion (cadar bindings) env)
`(let* (,@(mapcar #'list dummies vals)
,(car new))
(flet ((,accessor ()
,getter)
((setf ,accessor) (,value)
(setf ,(car new) ,value)
,setter
,value))
(symbol-macrolet ((,(caar bindings) (,accessor)))
(place-let* ,(cdr bindings) ,@body))))))
`(progn ,@body)))
One question, do I need the ,value at the end of my (setf ,accessor)
function, or is the setter returned by the get-setf-expansion always
supposed to return the proper value.
Thanks,
David
I haven't worked much with setf expansions, but setf itself is supposed
to always return the value that it has actually set, which may not be
the value that you passed to setf (!!!).
Silly example:
(defvar *x* 0)
(defun (setf x) (new-value) (setf *x* (1+ new-value)))
> (setf (x) 4)
5
> (setf (x) 42)
43
Returning 4 or 42 respectively here would potentially turn to wrong
assumptions about the values stored in some places.