Some time ago, I wrote these, and put them in my customized lisp,
but I have not used them much since then.
(defun letf-bindings (bindings environment)
(let ((savers ())
(setters ())
(restorers ()))
(loop
for (place values-form) in bindings do
(multiple-value-bind (vars vals stores setter getter)
(get-setf-expansion place environment)
(let ((save (gensym))
(store (first stores))
(multiple-values (rest stores)))
(setf savers (nconc (nreverse (mapcar #'list vars vals)) savers))
(push `(,save ,(if multiple-values
`(multiple-value-list ,getter)
getter))
savers)
(push (if multiple-values
`(multiple-value-bind ,stores ,values-form ,setter)
`(let ((,store ,values-form)) ,setter))
setters)
(push (if multiple-values
`(multiple-value-bind ,stores (values-list ,save) ,setter)
`(let ((,store ,save)) ,setter))
restorers))))
(values (nreverse savers) (nreverse setters) (nreverse restorers))))
(defmacro letf* (bindings &body body &environment environment)
"Simulate serial shallow binding of places in BINDINGS around BODY."
(if bindings
(multiple-value-bind (savers setters restorers)
(letf-bindings (list (first bindings)) environment)
`(let* (,@savers)
,@setters
(unwind-protect
,`(letf* ,(rest bindings) ,@body)
,@restorers)))
`(progn ,@body)))
(defmacro letf (bindings &body body &environment environment)
"Simulate parallell shallow binding of places in BINDINGS around BODY."
(if bindings
(multiple-value-bind (savers setters restorers)
(letf-bindings bindings environment)
`(let* (,@savers)
,@setters
(unwind-protect
(progn ,@body)
,@restorers)))
`(progn ,@body)))
#:Erik
--
If this is not what you expected, please alter your expectations.
Thanks! This is exactly what I was looking for.
--
Ivar
(defmacro letf (bindings &body body &environment environment)
"Simulate parallel shallow binding of places in BINDINGS around BODY."
(if bindings
(multiple-value-bind (savers setters restorers)
(letf-bindings bindings environment)
`(let* (,@savers)
;; ,@setters <-- remove this line
(unwind-protect
(progn
,@setters ;; <--- insert it here
,@body)
,@restorers)))
`(progn ,@body)))
The problem with LETF (as written) is that it will not work in a
multitasking environment, so be careful with it.
Doing so gives you nothing but a false sense of improved security.
| The problem with LETF (as written) is that it will not work in a
| multitasking environment, so be careful with it.
It will work exactly as well as, and no better than, doing the same
settings "manually", i.e., without the macro. This is intentional.
The problem is not with letf, but with setting such places. This is
actually very important to understand: letf does not make it worse,
but it also cannot protect you from the problem.
> * Joe Marshall <jmar...@alum.mit.edu>
> | I suggest that you move the setter forms to within the unwind-protect:
>
> Doing so gives you nothing but a false sense of improved security.
True, there are still potential holes, but moving the setters to
within the unwind-protect will at least make an *attempt* to recover
that should succeed in a variety of situations where having it outside
will fail.
Suppose we have a LETF like this:
(letf (((xyzzy y) 22)
((car (fun x)) (foobar a b c)))
(do-something))
Here are the failure modes:
1. A `value' cannot be computed. For instance, (foobar a b c)
throws an error. Since all values are computed before any
binding, these forms do not need to be protected.
2. A `place' cannot be computed. For instance, (fun x) throws an
error. Since all places are computed before any binding, these
forms do not need to be protected.
3. The `access' cannot be computed. For instance, (xyzzy y) throws
an error, or (fun x) does not return a cons. Since all values
are computed before any binding, these forms do not need to be
protected.
4. The `store' cannot be computed. For instance, the implied
rplaca or (setf xyzzy) throws an error. Should one of these
forms throw an error, that particular binding will not have
happened, but prior bindings will.
5. The `store' is not idempotent. For instance, (setf xyzzy) acts
differently each time it is called. In this case you are
probably hosed.
Now the original macro computes the values, the places, the accesses
and the first set of stores outside the unwind protect. Should some
of the stores run, but one throws out, the stores that have already
been done will not be undone since we are outside the unwind-protect.
However, by placing the stores inside the unwind-protect, should one
of the stores error out, we will run the cleanup forms. Of course the
cleanup forms run the same stores, so it is virtually certain that the
cleanup forms will error out as well, *but probably at the same form*.
This is the key. Most of the SETF store functions will be no more
complicated than RPLACA (in a typical case). So it is highly likely
that the ones able to run to completion upon entry they will be able
to run to completion on exit.
We are also relying on the store functions being run in the same order
when binding and unbinding. If the order were reversed, for example,
the above argument doesn't hold.
Also consider manually aborting out of the first set of store
functions (I think a more likely scenario than erroring out). Even if
only some of the modifications run, and all of the `unmodifications'
run, it is presumably ok to store the original value `on top of
itself'.
So although moving the store functions to inside the unwind-protect is
no panacea, it will likely make your life easier.
> | The problem with LETF (as written) is that it will not work in a
> | multitasking environment, so be careful with it.
>
> It will work exactly as well as, and no better than, doing the same
> settings "manually", i.e., without the macro. This is intentional.
> The problem is not with letf, but with setting such places. This is
> actually very important to understand: letf does not make it worse,
> but it also cannot protect you from the problem.
Yes. The problem is that LETF *looks* like it is binding a location
similarly to how LET binds special variables. (Indeed, that is the
point, is it not?) In this case looks are deceiving, and it may
perhaps be better to avoid using LETF in favor of an unwind-protect
and a pair of SETF's because the latter are less likely to mislead a
casual viewer.
> #:Erik
> --
> If this is not what you expected, please alter your expectations.
I wasn't expecting the Spanish Inquisition.
they don't seem to deal with declarations properly. This can be
particularly important in LET and LET* forms. Is it a finite amount of
work to get the basic declarations to work with LETF(*) as defined in
the previous post?
thanks,
dave
The places they are supposed to be used on are not variables that
will be used in the body, so declarations are less relevant than in
most other places. It is of course a finite amount of work to make
declarations work: Wrap a let form around the body instead progn, as
always when you want declarations to work.
> declarations work: Wrap a let form around the body instead progn, as
> always when you want declarations to work.
yeah. thanks. that's a very simple way to handle it that I missed.
dave
> * Joe Marshall <jmar...@alum.mit.edu>
> | I would imagine it is because Allegro CL doesn't handle LOCALLY
> | declarations correctly.
>
> Say what? I haven't even used locally sufficiently to realize there
> could be problems with it if I did. How should something you claim
> that I cannot even verify affect my decisions not to _start_ using
> something?
Verifying whether LOCALLY works or not is easy, see below.
It is certainly plausible that at some time in the past you
experimented with using LOCALLY, found it didn't do what you wanted,
and therefore made the decision not to _start_ using it.
Be that as it may, I was indulging in idle speculation. But my
statement was not devoid of value: there is a bug in how Allegro CL
handles LOCALLY.
> Sorry, but your thinking is just too weird for me.
No need to apologize.
> Incidentally, where I have used locally, which is not in macros, it
> has worked as I have wanted it to. Fault my understanding of how it
> _should_ work if you have to publish more of your imagination, but
> perhaps you could elaborate on the substance of your claim instead
> of making up silly and imaginary consequences of it?
I draw your attention to the disassembly of following the two segments
of code. The LOCALLY used in the first example provides sufficient type
information for the compiler to be able to optimize the call to AREF.
As you can see from the output of the compiler and the disassembled
code, the type declarations in test-one are not used during the
compilation of AREF.
Granted, the compiler is always free to ignore declarations (except
for special ones, of course), but given that the compiler does produce
optimized code when the declarations follow a LET statement, and fails
to do so when the declarations follow a LOCALLY, it is fair to say
that LOCALLY isn't working.
(in-package "USER")
(eval-when (:compile-toplevel :load-toplevel :execute)
(deftype array-index ()
`(INTEGER 0 ,array-dimension-limit))
)
(defun test-one (y x)
(check-type x array-index)
(check-type y (simple-array t (*)))
(locally (declare (type array-index x)
(type (simple-array t (*)) y)
(optimize (speed 3) (safety 1))
#+allegro (:explain :calls :types))
(aref y x)))
;Examining a call to AREF with arguments:
; symeval Y type T
; symeval X type T
; which returns a value of type T
;Generating a non-inline NON-SELF tail jump to AREF
;;; 157: 8b c3 movl eax,ebx
;;; 159: 8b 55 e4 movl edx,[ebp-28] ; X
;;; 162: c9 leave
;;; 163: 8b 5e 42 movl ebx,[esi+66] ; AREF
;;; 166: ff 67 27 jmp *[edi+39] ; SYS::TRAMP-TWO
(defun test-two (y x)
(check-type x array-index)
(check-type y (simple-array t (*)))
(let ((x x)
(y y))
(declare (type array-index x)
(type (simple-array t (*)) y)
(optimize (speed 3) (safety 1))
#+allegro (:explain :calls :types))
(aref y x)))
;Examining a call to AREF with arguments:
; symeval Y type (SIMPLE-ARRAY T (*))
; symeval X type (INTEGER 0 16777216)
; which returns a value of type T
;;; 157: 8b 55 e4 movl edx,[ebp-28] ; X
;;; 160: 8b 44 13 12 movl eax,[ebx+edx+18]
;;; 164: f8 clc
;;; 165: c9 leave
;;; 166: 8b 75 fc movl esi,[ebp-4]
;;; 169: c3 ret
> Granted, the compiler is always free to ignore declarations (except
> for special ones, of course), but given that the compiler does produce
> optimized code when the declarations follow a LET statement, and fails
> to do so when the declarations follow a LOCALLY, it is fair to say
> that LOCALLY isn't working.
I don't think that's fair at all. If something is optional and a
vendor doesn't do it then they just don't do it. And ACL does listen
to SPECIAL declarations in LOCALLY.
--tim
Well, if declarations in LOCALLY are being pretty much ignored, you
could hardly say it *is* working.
How about `LOCALLY isn't working as one might expect it to'?
> * Joe Marshall <jmar...@alum.mit.edu>
> | How about `LOCALLY isn't working as one might expect it to'?
>
> I think this is a very good way to phrase your complaints, as it
> stresses your expectations, not how it _is_ working. I'm as
> interested as anyone in as powerful and useful behavior as possible
> in any Common Lisp system, but I'm also a language lawyer after many
> years of exposure to the standards processes of several languages,
> and there's not much to say on this except: only if you can
> demonstrate that your expectations are such that a failure to
> indulge same constitutes a violation of that one principally
> important property of an implementation of standard, namely
> conformance, you can no longer whack people over the head for
> breaking the promise to be conformant, you are discussing the
> quality of implementation, as determinable in a open marketplace
> where the standard and the conformance to it constitute a baseline.
I never said Franz wasn't conformant, I merely pointed out that they
don't *do* much with the declarations that follow a LOCALLY, and in
particular, they don't perform the optimizations they would perform if
the declarations were at the beginning of a lambda-body or a let.
>
> In short: Your expectations are demonstrably irrelevant.
>
The actual effect (or lack thereof) of a LOCALLY statement in Franz
Lisp is relevant.
> #:Erik
> --
> If this is not what you expected, please alter your expectations.
But they're irrelevant!
Joe, your analysis is slightly off. In the ACL compiler the effect of
declarations at the head of a LOCALLY form is exactly the same as at the
head of a LET or LAMBDA body.
What the compiler fails to do is to process _free_ type declarations.
Recall that a "free" declaration is one concerning about a binding made
at some contour interior to the one that established that binding. So:
(defun foo (x n)
(declare (optimize (speed 3) (safety 0)))
(let ()
(declare (type simple-string x))
(char x n)))
(defun foo (x n)
(declare (optimize (speed 3) (safety 0)))
(locally ()
(declare (type simple-string x))
(char x n)))
These forms are treated identically, and neither will be optimized the
way the programmer clearly intends. It is only free type declarations
that are ignored (other than syntax checking, etc.) and OPTIMIZE,
SPECIAL, and other free declaratione are processed as one would expect.
The only difference between a LOCALLY and a LET with no bindings is that
the former when used at top-level passes the quality of top-level-ness to
its body. This is as required by the ANS.
I think you, Erik, and I would all agree that it would be better if the
compiler observed free type declarations, just as we all agree it is not
required that it do so. I expect I am somewhat more critical of the
failure than Erik, however, because the failure mode is very obscure (e.g.
your analysis was incorrect) and the result is that the compiler does not
do what the programmer expects it to do. Furthermore, the circumstances
where the compiler doesn't do the right thing are very similar to the
circumstances in which it does do the right thing, and the failing
construct is one that is or ought to be in the vocabulary of programmers.
This glitch in the compiler is therefore pernicious to portability of code
and portability of programmers.
> Joe, your analysis is slightly off. In the ACL compiler the effect of
> declarations at the head of a LOCALLY form is exactly the same as at the
> head of a LET or LAMBDA body.
>
> What the compiler fails to do is to process _free_ type declarations.
Ah, now I understand what's going on there. Thank you for clarifying it.
> I think you, Erik, and I would all agree that it would be better if the
> compiler observed free type declarations, just as we all agree it is not
> required that it do so.
But if we all agreed with Erik, he'd have no fodder for his acerbic
philippics!
> I expect I am somewhat more critical of the failure than Erik,
> however, because the failure mode is very obscure (e.g. your
> analysis was incorrect) and the result is that the compiler does not
> do what the programmer expects it to do.
Other problems are that although the failure mode is obscure, the
idiom that generates it is common, and there isn't an easy workaround
(other than decorating your code with lots of `THE' expressions).
Given that Allegro does a good job with type inference in other
situations, I would guess that Franz intended or intends to process
free type declarations, and would thus see this as a bug, even though
it techically does not produce incorrect code.
_That_ is just Wrong, too! Argh!
Could you point out where in the standard it indicates that an
implementation may ignore a free type declaration if it obeys a
bound one?
Thanks,
/Jon
--
Jon Anthony
Synquiry Technologies, Ltd. Belmont, MA 02478, 617.484.3383
"Nightmares - Ha! The way my life's been going lately,
Who'd notice?" -- Londo Mollari
> * Joe Marshall <jmar...@alum.mit.edu>
> | But if we all agreed with Erik, he'd have no fodder for his acerbic
> | philippics!
>
> _That_ is just Wrong, too! Argh!
>
I stand corrected.
This is a completely bogus question. Pull yourself together and
_think_ carefully about what you request from the standard, and to
what level of redundant detail it had to go if it were able to
answer your question.
#:Erik, sighing
Yes, it is if you misunderstand the intent. I realize what your point
is here - the standard doesn't have to explicitly state this (then
again maybe you mean more than this and I really am totally lost.)
> and to what level of redundant detail it had to go if it were able
> to answer your question.
That's exactly right - I don't expect such redundant detail, and
that's why I didn't ask for "where it _says_" this. What I was
"admitting to" was that I didn't see where/how this comes about.
Certainly 3.3.1 says an implementation is free to ignore any type
declaration specifier anywhere, and thus such free ones can be
ignored. But I had the (mis?)impression that something more was being
indicated by S Haflich.
> Could you point out where in the standard it indicates that an
> implementation may ignore a free type declaration if it obeys a
> bound one?
An implementation is free to ignore *all* type declarations, or not,
as it chooses. An implementation is perfectly entitled to listen to
type declarations only on Thursday afternoons when the moon is full.
--tim
Others have already replied as to the text of the ANS, but in addition,
think about the issue from the underside:
A correct type declaration never has any semantic effect on a correct
program. (Obvious exception: A slot type declaration affects what is
returned by slot-definition-type, but that is visible only using the
MOP.) If you can tell whether the implementation did or did not
observe a type declaration, using only behavior mandated in the ANS,
then your program and/or its declarations were incorrect and violated
the standard.
An implementation may ignore any type declaration whenever it likes
because no type declaration can have any observable semantic effect
on correct code. A correct type declaration may have effect on speed
or space or whatever, but not semantics.
> A correct type declaration never has any
semantic effect on a correct
> program. (Obvious exception: A slot type
declaration affects what is
> returned by slot-definition-type, but that is
visible only using the
> MOP.) If you can tell whether the
implementation did or did not
> observe a type declaration, using only behavior
mandated in the ANS,
> then your program and/or its declarations were
incorrect and violated
> the standard.
Yes, this is all very straight forward and
standard stuff.
> An implementation may ignore any type
declaration whenever it likes
> because no type declaration can have any
observable semantic effect
> on correct code. A correct type declaration may
have effect on
> speed or space or whatever, but not semantics.
Yes, again this is clear (and a reasonable
_rationale_ for why the
rule in 3.3.1 exists).
I think the whole thing boils down to my thinking
that the (rather
extensive) exchanges by folks I consider
knowledgeable indicated that
there was something more to all of this than that
ACL simply took
advantage of the rule in one context and not
another. That appears to
have been the major error on my part.
/Jon
--
Jon Anthony
Synquiry Technologies, Ltd. Belmont, MA 02478,
617.484.3383
"Nightmares - Ha! The way my life's been going
lately,
Who'd notice?" -- Londo Mollari
Sent via Deja.com http://www.deja.com/
Before you buy.
> I think the whole thing boils down to my thinking
> that the (rather
> extensive) exchanges by folks I consider
> knowledgeable indicated that
> there was something more to all of this than that
> ACL simply took
> advantage of the rule in one context and not
> another. That appears to
> have been the major error on my part.
Well, IMHO the choice isn't quite as arbitrary as that: Since the
HyperSpec already differentiates between free and bound declarations
(not only type declarations) for the purposes of scope, this is a
less arbitrary boundary to draw than others. Also note that there is
another important distinction between bound and free declarations:
You can only have one bound declaration of any sort for any binding,
whereas you can have many free declarations for a binding. Or to put
it another way: Free declarations introduce their own scoping system,
whereas bound declarations live off the scoping system already in
place for bindings. Thus it is a "natural" limit for the support of
declarations. ACL isn't the only implementation that sets a limit
there: CMUCL for example doesn't heed free ignore and ignorable
declarations, as do many other implementations.
Regs, Pierre.
--
Pierre Mai <pm...@acm.org> PGP and GPG keys at your nearest Keyserver
"One smaller motivation which, in part, stems from altruism is Microsoft-
bashing." [Microsoft memo, see http://www.opensource.org/halloween1.html]
No where, in anything I said, do I indicate that this choice was
arbitrary. There may well be very good reasons for ACL doing this (as
you indicate) and that is why it would be "taking advantage of" and
not simply using (arbitrarily) the rule.
> Pierre R. Mai wrote:
> >
> > jsa...@my-deja.com writes:
> >
> > > there was something more to all of this than that ACL simply took
> > > advantage of the rule in one context and not another. That appears
> > > to have been the major error on my part.
> >
> > Well, IMHO the choice isn't quite as arbitrary as that:
>
> No where, in anything I said, do I indicate that this choice was
> arbitrary. There may well be very good reasons for ACL doing this (as
> you indicate) and that is why it would be "taking advantage of" and
> not simply using (arbitrarily) the rule.
I'm sorry if I misunderstood/misrepresented your comments.
> * Joe Marshall <jmar...@alum.mit.edu>
> | How about `LOCALLY isn't working as one might expect it to'?
>
> I think this is a very good way to phrase your complaints, as it
> stresses your expectations, not how it _is_ working. I'm as
> interested as anyone in as powerful and useful behavior as possible
> in any Common Lisp system, but I'm also a language lawyer after many
> years of exposure to the standards processes of several languages,
> and there's not much to say on this except: only if you can
> demonstrate that your expectations are such that a failure to
> indulge same constitutes a violation of that one principally
> important property of an implementation of standard, namely
> conformance, you can no longer whack people over the head for
> breaking the promise to be conformant, you are discussing the
> quality of implementation, as determinable in a open marketplace
> where the standard and the conformance to it constitute a baseline.
Oh my god. Can you say "big fat run-on sentence"? :) I hope English isn't
one of the languages you are a lawyer for.
--
Kirk Saranathan ksar...@yahoo.com
Key ID: 6B287105 Public key at http://www.keyserver.net
Fingerprint = C826 8E65 3A28 29A1 EDF1 19FD 6B42 6BD1 6B28 7105
[...]
> Oh my god. Can you say "big fat run-on sentence"? :) I hope English
> isn't one of the languages you are a lawyer for.
>
> --
> Kirk Saranathan
You have every right to specify sentence length limits, as your email
name is also an environmentally friendly abbreviation of your big, fat,
run-on name :-)
Robert