From: Stephen W. <wi...@mu...> - 2006-12-30 04:23:16
|
Hello, Duplicate go-tags are not allowed in SBCL. However, (tagbody :foo :foo) goes undetected and results in unbounded recursion within the compiler. Any two consecutive duplicate go-tags result in this behavior. A simple fix is below. Tested on an i686-pc-linux-gnu. I also noticed that NIL is not recognized as a valid go-tag, though my reading of the standard suggests it should be. I can attempt to resolve this over the weekend (however, my experience with SBCL internals is near zero). Thanks, Steve diff -u -a -r1.75 ir1-translators.lisp --- ir1-translators.lisp 26 Oct 2006 11:37:57 -0000 1.75 +++ ir1-translators.lisp 30 Dec 2006 02:41:52 -0000 @@ -146,7 +146,7 @@ (segments `(,@current nil)) (return)) (let ((tag (elt current tag-pos))) - (when (assoc tag (segments)) + (when (or (assoc tag (segments)) (eql tag (car current))) (compiler-error "The tag ~S appears more than once in the tagbody." tag)) |
From: Stephen W. <wi...@mu...> - 2006-12-30 22:31:34
Attachments:
parse-tagbody.patch
|
Greetings, Stephen Wilson <wi...@mu...> writes: > I also noticed that NIL is not recognized as a valid go-tag, though my > reading of the standard suggests it should be. I can attempt to > resolve this over the weekend (however, my experience with SBCL > internals is near zero). The problem: * (tagbody (go nil) nil) ; in: LAMBDA NIL ; (GO NIL) ; ; caught ERROR: ; attempt to GO to nonexistent tag: NIL ; ; compilation unit finished ; caught 1 ERROR condition but NIL is a valid go tag. I reworked PARSE-TAGBODY to accept the NIL go-tag. This subsumes my previous patch to handle duplicate labels properly. I extended the comment preceeding this function to draw attention to the distinguished use of NIL as a tag for the initial tagbody segment (or prelude) -- noting that it may be used as a tag in an `inner' segment. The error message in the case when a go tag is neither an integer or a symbol is misleading. Peviously, a go tag is called a statement. In the hyperspec, a statement in a tagebody is a compound form, not a go tag. I reformated the error message to conform. Otherwise, PARSE-TAGBODY behaves as the previous version. Tested on an i686-pc-linux-gnu. Thanks, Steve |
From: Stephen W. <wi...@mu...> - 2006-12-30 23:55:05
Attachments:
parse-tagbody.patch
|
Greetings, My previous message seems not to have gotten through. This is a resend of the original, with a small correction over the previous patch: (or (member tag (tags))) ==> (member tag (tags)) --------------------------------------------------------------- Greetings, Stephen Wilson <wi...@mu...> writes: > I also noticed that NIL is not recognized as a valid go-tag, though my > reading of the standard suggests it should be. I can attempt to > resolve this over the weekend (however, my experience with SBCL > internals is near zero). The problem: * (tagbody (go nil) nil) ; in: LAMBDA NIL ; (GO NIL) ; ; caught ERROR: ; attempt to GO to nonexistent tag: NIL ; ; compilation unit finished ; caught 1 ERROR condition but NIL is a valid go tag. I reworked PARSE-TAGBODY to accept the NIL go-tag. This subsumes my previous patch to handle duplicate labels properly. I extended the comment preceeding this function to draw attention to the distinguished use of NIL as a tag for the initial tagbody segment (or prelude) -- noting that it may be used as a tag in an `inner' segment. The error message in the case when a go tag is neither an integer or a symbol is misleading. Peviously, a go tag is called a statement. In the hyperspec, a statement in a tagebody is a compound form, not a go tag. I reformated the error message to conform. Otherwise, PARSE-TAGBODY behaves as the previous version. Tested on an i686-pc-linux-gnu. Thanks, Steve |
From: Stephen W. <wi...@mu...> - 2006-12-31 16:51:32
Attachments:
parse-tagbody.patch
|
Greetings, I seem to be experiencing mail troubles. This messages may appear more than once. Stephen Wilson <wi...@mu...> writes: > I also noticed that NIL is not recognized as a valid go-tag, though my > reading of the standard suggests it should be. I can attempt to > resolve this over the weekend (however, my experience with SBCL > internals is near zero). The problem: * (tagbody (go nil) nil) ; in: LAMBDA NIL ; (GO NIL) ; ; caught ERROR: ; attempt to GO to nonexistent tag: NIL ; ; compilation unit finished ; caught 1 ERROR condition but NIL is a valid go tag. I reworked PARSE-TAGBODY to accept the NIL go-tag. This subsumes my previous patch to handle duplicate labels properly. I extended the comment preceeding this function to draw attention to the distinguished use of NIL as a tag for the initial tagbody segment (or prelude) -- noting that it may be used as a tag in an `inner' segment. The error message in the case when a go tag is neither an integer or a symbol is misleading. Peviously, a go tag is called a statement. In the hyperspec, a statement in a tagebody is a compound form, not a go tag. I reformated the error message to conform. Otherwise, PARSE-TAGBODY behaves as the previous version. Tested on an i686-pc-linux-gnu. Thanks, Steve |
From: Juho S. <js...@ik...> - 2007-01-02 20:28:57
|
Stephen Wilson <wi...@mu...> writes: > Stephen Wilson <wi...@mu...> writes: > > I also noticed that NIL is not recognized as a valid go-tag, though my > > reading of the standard suggests it should be. I can attempt to > > resolve this over the weekend (however, my experience with SBCL > > internals is near zero). > > The problem: > > * (tagbody (go nil) nil) > ; in: LAMBDA NIL > ; (GO NIL) > ; > ; caught ERROR: > ; attempt to GO to nonexistent tag: NIL > ; > ; compilation unit finished > ; caught 1 ERROR condition > > but NIL is a valid go tag. > > I reworked PARSE-TAGBODY to accept the NIL go-tag. This subsumes my > previous patch to handle duplicate labels properly. Thanks, that looks good. Could you add a test for both of the fixed bugs to the test suite? eval.impure.lisp might be a reasonable file. Btw, I believe the duplicate tag issue also exists in the interpreter. -- Juho Snellman |
From: Stephen W. <wi...@mu...> - 2007-01-03 04:52:10
Attachments:
eval.impure.patch
|
Juho Snellman <js...@ik...> writes: > Could you add a test for both of the fixed > bugs to the test suite? eval.impure.lisp might be a reasonable > file. I do not fully understand the test framework, and am unsure if the patch below is appropriate. The test function TAGBODY-DUAL-GO-TAGS hangs an unpatched SBCL. I do not know how to properly test this situation. I wrote the test case assuming that it would fail under a patched SBCL. Otherwise, running `sh ./run-tests.sh eval.impure.lisp` on both an unpatched and patched SBCL gives appropriate results. > Btw, I believe the duplicate tag issue also exists in the > interpreter. I do not think the interpreter has this issue. (am I missing something?): CL-USER> (setq sb-ext:*evaluator-mode* :interpret) :INTERPRET CL-USER> (tagbody :A :A) NIL CL-USER> (tagbody (go nil) nil) NIL Note that, in the interpreter, it is not an error for there to be duplicate go tags. I was not aware of this divergent behavior before. However, I believe the interpreter has the correct behavior. There are other issues tied to this (general binding semantics, esp. lambda args/parsing, all ANSI issues), which I can write about shortly. Thanks, Steve > -- > Juho Snellman |
From: Christophe R. <cs...@ca...> - 2007-01-05 16:55:14
|
Stephen Wilson <wi...@mu...> writes: > On the other hand, Lisp is a very robust language. It has been > designed not to error out except in truly erroneous situations. I can > get past my subjective dislike for duplicate parameters by seeing this > as a `robustness' issue. OK, I think we're getting to the core of the disagreement now. Here's some of it: > The way in which I deal with complaints raised by a compiler is not > too different regarding whether it is a compiler error or warning. > Machine generated Lisp code cannot respond to an error. This is not so, I think: it can handle errors in the same way as it can handle all sorts of conditions, and signalling an error allows the program generating the lisp code to recover in ways natural to that program. > > I think that the absence of specification of what happens when some of > > the varj have the same name is telling. > > Ya, so do I. Too bad we are getting different messages :) I don't think we are: I think we agree that in the case of LET, the standard does not specify what happens if names are the same. The difference lies in what we want to do about it: you want to be permissive, and infer what the behaviour should be from that of similar constructs, which you claim to be sufficiently unambiguously specified to make this inference. I want to assist the user in writing or generating code that will run not just in one version of one implementation, but in all versions of all implementations (modulo bugs), and if that means that I cause errors to be signalled in cases where it would be possible to return one of many possible answers, then so be it. That is, I am prepared to accept a certain loss of more-or-less ill-defined programs (I'll agree that we're still disagreeing on whether same-named required parameters in a lambda list are in fact ill-defined) if that assists in identifying programs which are guaranteed to run not just now but in ten years' time; particularly if there's no strong agreement from other implementations currently over the meaning of the ill-defined program. > iii) The introduction of an erroneous state in an otherwise well > defined situation is not ideal as it leads to a less robust system. If you consider a run-time error to be something utterly fatal, then I can see some of where you're coming from; however, I think that it is preferable to signal an error than generate an arbitrary, unpredictable result, and I would say that silently returning nonsense leads to significantly less robust systems than signalling errors. Generating only-maybe-legal Lisp code and running it expecting it never to signal an error is not robust against changes to lisp implementations, and no amount of requesting implementations not to signal errors will make it in actual fact robust to new and changing implementations: only generating unambiguously specified Lisp code and running it stands a chance of doing that. > A warning is appropriate and more in line with the spirit of the > language. I think this might have been true once, before the development of the condition system; however, given the condition system as a mechanism for conveying information about exceptional situations from and to different parts of a program considered as a whole -- including portions generated at runtime -- I no longer think it is true. > v) For TAGBODY and go tags, the guiding principle should be > consistency across `similar' constructs which introduce bindings. To > my mind, the behaviour should be: > > (tagbody (foo) :A (bar) :B (baz) :A (jar)) ==> > (tagbody (foo) (bar) :B (baz) :A (jar)) > > In other words, go tag bindings should be introduced from left to > right, where rightmost shadows duplicates on the left, and where an > appropriate warning is issued. Again, I think that an error is the right answer, consistently with the guiding principle that that which is undefined (and does not substantially add to expressivity) should be signalled as such in no uncertain terms. Cheers, Christophe |
From: Stephen W. <wi...@mu...> - 2007-01-05 18:11:20
|
Christophe Rhodes <cs...@ca...> writes: > > The way in which I deal with complaints raised by a compiler is not > > too different regarding whether it is a compiler error or warning. > > Machine generated Lisp code cannot respond to an error. > > This is not so, I think: it can handle errors in the same way as it > can handle all sorts of conditions, and signalling an error allows the > program generating the lisp code to recover in ways natural to that > program. OK. Sure it is not impossible fundamentally. But it is complicating especially when the cause of an error cannot be foreseen. >I think we agree that in the case of LET, the standard does not >specify what happens if names are the same. The difference lies in >what we want to do about it: you want to be permissive, and infer >what the behaviour should be from that of similar constructs, which >you claim to be sufficiently unambiguously specified to make this >inference. I want to assist the user in writing or generating code >that will run not just in one version of one implementation, but in >all versions of all implementations (modulo bugs), and if that means >that I cause errors to be signalled in cases where it would be >possible to return one of many possible answers, then so be it. > > That is, I am prepared to accept a certain loss of more-or-less > ill-defined programs (I'll agree that we're still disagreeing on > whether same-named required parameters in a lambda list are in fact > ill-defined) if that assists in identifying programs which are > guaranteed to run not just now but in ten years' time; particularly if > there's no strong agreement from other implementations currently over > the meaning of the ill-defined program. I see your point here, and I can accept it. I make one last point below. > If you consider a run-time error to be something utterly fatal, then I > can see some of where you're coming from; however, I think that it is > preferable to signal an error than generate an arbitrary, > unpredictable result, and I would say that silently returning nonsense > leads to significantly less robust systems than signalling errors. For the bahaviour of LET and TAGBODY im advocating consistant semantics w.r.t LAMBDA. I do feel LAMBDA is well defined. I dont think the results are nonsense but rather sensible. Note too that I am advocating that errors be replaced with warnings. On the other hand, other lisps do return nonsense. I can see the pratical concern in providing an implmentation which preserves the semantics of well understood constructs but which bails where there is no concensus among implementations. > Generating only-maybe-legal Lisp code and running it expecting it > never to signal an error is not robust against changes to lisp > implementations, and no amount of requesting implementations not to > signal errors will make it in actual fact robust to new and changing > implementations: only generating unambiguously specified Lisp code and > running it stands a chance of doing that. I do agree, but only w.r.t LET and TAGBODY using the practical point of view your advocating. As the risk of being redundant, could I ask why you feel duplicate required parameters are not defined? I still have not seen an explanation. It seems to me that `duplicate args' is an artifical concept in the sense that it is not treated explicitly by the standard - nor does it have to. A parameter is a parameter, there is no technical reason to label a parameter as being the `duplicate' of another. I would imagine that this is why we dont have a clause in the spec which mentions duplicate args as a special case. Why bother if they are already covered by the definition? Why complicate the spec by introducing redundant concepts? Special cases are defined and mentioned when the behaviour is not specified or in error. On the other hand, I cant make a technical argument for LET and TAGBODY. I can appeal only to the lambda/let connection and consistancy considerations. The practical points you mention are equally valid. That being said, SBCL is consistant (at least in compiled code) w.r.t its treatment of duplicate tags/bindings etc. Changing only how parameters are processed (in line with my stance) breaks this consistancy and is somthing I would not like to see. Thanks alot for the discussion. I hope that this hasnt come across as just a bunch of nit picking and noise. Sincerely, Steve |
From: Stephen W. <wi...@mu...> - 2007-01-05 18:54:36
|
wil...@ai... (William Harold Newman) writes: > To me, the ANSI specification of LET seems to assume that duplicate > name binding is nonsense. It doesn't explicitly forbid it, but neither > does it bother to think about what takes precedence when duplicate > names are used; this would be a serious oversight if duplicate names > were intended. I don't think it's much of a stretch instead to > interpret "binding in parallel" to mean that duplicate names are > nonsense, more or less as in casual mathematical conversation: we > don't say "let x be the square root of height, y be the square root of > area, and x be the cube root of height." The way that different CL > implementations resolve the precedence in different ways is further > evidence that we won't be doing the portability-minded user a favor by > silently accepting the input. I have come to agree with this in principle. The only thing I need to come to terms with is why an error is more appropriate than a warning. > Beyond positional arguments, though, it gets more complicated. By the > usual analogy between LET and function calls, it would be simple to > say that *all* function arguments are bound in parallel. I think of LAMBDA as being more general than a LET in that the former can emulate the latter. So instead of saying the parallel binding of LET should determin the processing of LAMBDA parameters, ideally I would prefer LAMBDA semantics to determine that of LET. > Unfortunately, &AUX arguments seem to be intended not to be bound in > parallel, and it is very common (and as useful) to initforms for > &OPTIONAL and &KEY arguments which refer to the bindings of earlier > arguments. &AUX parameters are specifically noted as not being `true' parameters, and are subject to a different processing rules than other paramters. > My best guess at the moment is that: > * LET should signal an error on duplicate names. > * Lambda list positional arguments should act like LET arguments, > while lambda list arguments which accept initforms should act > like LET* arguments nested inside the LET, so > (DEFUN FROB (V W X &OPTIONAL (Y (SQRT X)) &KEY (Z (SQRT Y))) > ...) > behaves rather like > (LET ((V ...) (W ...) (X ...)) > (LET* ((Y ...) (Z ...)) > ...)). > * Whether or not &KEY arguments allow binding the same variable > name more than once, it is probably an error to use duplicate > keywords. > > From that point of view it's probably not an error to do something > like > (DEFUN FROB (X &OPTIONAL X &KEY X) ...), > strange though that is; but it probably is an error to do > (DEFUN FROB (X &OPTIONAL X &KEY X X) ...) > or just > (DEFUN FROB (&KEY X X) ...). I dont like defining LAMBDA in terms of LET. LAMBDA is better defined. For the sake of argument, lets drop the notion of `duplicate parameters'. All we have are plain parameters, processed from left to right, where I read `processed' to include the act of binding. This is, I belive, compliant with the standard. So then (DEFUN FROB (X &OPTIONAL X &KEY X X) X) is well defined. It always returns NIL except when a key pair for :X is supplied. > I don't like (DEFUN FROB (X &OPTIONAL X &KEY X) ...), though, and if I > were writing the spec I'd explicitly forbid it, so if someone finds a > place where it's explicitly forbidden, I'll be happy. I dont like it either, and I would thrash the user with a stern warning. But I really dont think this is undefined. Thanks, Steve |
From: James Y K. <fo...@fu...> - 2007-01-05 19:40:50
|
On Jan 5, 2007, at 2:04 PM, Stephen Wilson wrote: >> I don't like (DEFUN FROB (X &OPTIONAL X &KEY X) ...), though, and >> if I >> were writing the spec I'd explicitly forbid it, so if someone finds a >> place where it's explicitly forbidden, I'll be happy. > > I dont like it either, and I would thrash the user with a stern > warning. But I really dont think this is undefined. That nobody can seem to agree on what the correct behavior is, or even if it is undefined, seems like a pretty good indication that it is, actually, undefined. And really, anyone who intentionally writes code such as that deserves what they get. But why is this even being discussed? Has there ever been real code this has broken, where said real code wasn't already buggy because of this? I doubt it. +1 for keeping it an error from me. Errors are good. This seems like a silly theoretical exercise, where the result of the change being discussed could not ever actually be useful. James |
From: <wil...@ai...> - 2007-01-06 16:27:26
|
On Fri, Jan 05, 2007 at 02:04:08PM -0500, Stephen Wilson wrote: > wil...@ai... (William Harold Newman) writes: > > To me, the ANSI specification of LET seems to assume that duplicate > > name binding is nonsense. It doesn't explicitly forbid it, but neither > > does it bother to think about what takes precedence when duplicate > > names are used; this would be a serious oversight if duplicate names > > were intended. I don't think it's much of a stretch instead to > > interpret "binding in parallel" to mean that duplicate names are > > nonsense, more or less as in casual mathematical conversation: we > > don't say "let x be the square root of height, y be the square root of > > area, and x be the cube root of height." The way that different CL > > implementations resolve the precedence in different ways is further > > evidence that we won't be doing the portability-minded user a favor by > > silently accepting the input. > > I have come to agree with this in principle. The only thing I need to > come to terms with is why an error is more appropriate than a warning. I have a strong preference for failing pretty hard here. The bigger a software project gets (in lines of code, in years of life, and/or in number of target systems) the more trouble your system software can cause by quietly DWIM when the behavior is underspecified. Either an ERROR or a full compiler WARNING (not just STYLE-WARNING) is probably a sufficiently hard failure for this purpose, because any project large enough that it uses an automated build system (ASDF or whatever) tends to treat COMPILE returning FAILURE-P as a hard failure. I have a weaker but still clear preference for using ERROR instead of WARNING. After signalling ERROR we've finished solving the problem; after signalling WARNING, we still have to try to guess what behavior the user might mean, implement it, and document it. It seems that for this multiple binding ambiguity it's hard to make a very good guess, especially since one obvious behavior the user might want is "do the same thing as the implementation J that I am porting this code from" and as an earlier message, implementations J and J' do different things, and even implementation J itself does different things in compiled vs. interpreted code. It also seems to me that there's very little benefit to making a good guess; I'm having trouble seeing a situation where the user will say "ah, good, SBCL intuits what I mean, that's all right then" instead of just saying "oops" and rewriting the code to remove the ambiguity. -- William Harold Newman <wil...@ai...> PGP key fingerprint 85 CE 1C BA 79 8D 51 8C B9 25 FB EE E0 C3 E5 7C The intersection of incompatible types is NIL; the intersection of incompatible behaviors is ERROR. |
From: Tim B. <tf...@tf...> - 2007-01-07 16:29:43
|
On 5 Jan 2007, at 19:04, Stephen Wilson wrote: > I have come to agree with this in principle. The only thing I need to > come to terms with is why an error is more appropriate than a warning. As a humble user, and not a developer of SBCL or any other Lisp, I would *much* prefer something that by default caused the compilation to fail. In almost all cases where duplicate argument names get into lambda lists it is due to an error (whether or not it is an error in the sense of the standard, it will be an error in real life), and the useful behaviour of the system is to refuse to compile such code, in my opinion. I'm trying to think of times when duplicate arguments might arise in a useful way, but I can't (duplicate keywords are a very different case of course). --tim |
From: Juho S. <js...@ik...> - 2007-01-03 20:27:56
|
Stephen Wilson <wi...@mu...> writes: > Juho Snellman <js...@ik...> writes: > > Could you add a test for both of the fixed > > bugs to the test suite? eval.impure.lisp might be a reasonable > > file. > > I do not fully understand the test framework, and am unsure if the > patch below is appropriate. > > The test function TAGBODY-DUAL-GO-TAGS hangs an unpatched SBCL. I do > not know how to properly test this situation. I wrote the test case > assuming that it would fail under a patched SBCL. Thanks, that looks fine (except for the gratuitous PROGNs in the WITH-TEST bodies, but that's just a minor style nit). > Note that, in the interpreter, it is not an error for there to be > duplicate go tags. I was not aware of this divergent behavior > before. However, I believe the interpreter has the correct behavior. > There are other issues tied to this (general binding semantics, > esp. lambda args/parsing, all ANSI issues), which I can write about > shortly. Ok, it would be good if you could expand a bit on that. I don't quite understand why signaling an error for duplicate tags in compiled code but not in interpreted would be the correct behaviour. (Beyond the "it's undefined, so anything we do is correct" cop out). -- Juho Snellman |
From: <wil...@ai...> - 2007-01-05 18:19:25
|
On Fri, Jan 05, 2007 at 04:54:42PM +0000, Christophe Rhodes wrote: > Stephen Wilson <wi...@mu...> writes: > > > On the other hand, Lisp is a very robust language. It has been > > designed not to error out except in truly erroneous situations. I can > > get past my subjective dislike for duplicate parameters by seeing this > > as a `robustness' issue. > > OK, I think we're getting to the core of the disagreement now. Here's > some of it: [...] > That is, I am prepared to accept a certain loss of more-or-less > ill-defined programs (I'll agree that we're still disagreeing on > whether same-named required parameters in a lambda list are in fact > ill-defined) if that assists in identifying programs which are > guaranteed to run not just now but in ten years' time; particularly if > there's no strong agreement from other implementations currently over > the meaning of the ill-defined program. Yes, to the extent that it's undefined behavior, treating it as an error instead of trying to DWIM is the usual way. I grant Stephen Wilson's point that the extent to which the standard leaves duplicate names in LET and lambda lists undefined isn't completely clear. To me, the ANSI specification of LET seems to assume that duplicate name binding is nonsense. It doesn't explicitly forbid it, but neither does it bother to think about what takes precedence when duplicate names are used; this would be a serious oversight if duplicate names were intended. I don't think it's much of a stretch instead to interpret "binding in parallel" to mean that duplicate names are nonsense, more or less as in casual mathematical conversation: we don't say "let x be the square root of height, y be the square root of area, and x be the cube root of height." The way that different CL implementations resolve the precedence in different ways is further evidence that we won't be doing the portability-minded user a favor by silently accepting the input. Beyond positional arguments, though, it gets more complicated. By the usual analogy between LET and function calls, it would be simple to say that *all* function arguments are bound in parallel. Unfortunately, &AUX arguments seem to be intended not to be bound in parallel, and it is very common (and as useful) to initforms for &OPTIONAL and &KEY arguments which refer to the bindings of earlier arguments. My best guess at the moment is that: * LET should signal an error on duplicate names. * Lambda list positional arguments should act like LET arguments, while lambda list arguments which accept initforms should act like LET* arguments nested inside the LET, so (DEFUN FROB (V W X &OPTIONAL (Y (SQRT X)) &KEY (Z (SQRT Y))) ...) behaves rather like (LET ((V ...) (W ...) (X ...)) (LET* ((Y ...) (Z ...)) ...)). * Whether or not &KEY arguments allow binding the same variable name more than once, it is probably an error to use duplicate keywords. >From that point of view it's probably not an error to do something like (DEFUN FROB (X &OPTIONAL X &KEY X) ...), strange though that is; but it probably is an error to do (DEFUN FROB (X &OPTIONAL X &KEY X X) ...) or just (DEFUN FROB (&KEY X X) ...). I don't like (DEFUN FROB (X &OPTIONAL X &KEY X) ...), though, and if I were writing the spec I'd explicitly forbid it, so if someone finds a place where it's explicitly forbidden, I'll be happy. -- William Harold Newman <wil...@ai...> PGP key fingerprint 85 CE 1C BA 79 8D 51 8C B9 25 FB EE E0 C3 E5 7C Ubi saeva indignatio ulterius cor lacerare nequit. -- Jonathan Swift's epitaph |
From: <bdo...@la...> - 2007-01-06 17:46:45
|
On Fri, Jan 05, 2007 at 12:20:42PM -0600, William Harold Newman wrote: > * Whether or not &KEY arguments allow binding the same variable > name more than once, it is probably an error to use duplicate > keywords. I think by the wording of the spec using duplicate keyword names in a lambda list (not duplicate variable names - that's just as uncertain as anything else in this discussion) is valid. For instance, (defun foo (&key ((:x x)) ((:x y))) (print (list x y))) 3.4.1.4 states: | The keyword parameter specifiers are, like all parameter specifiers, | effectively processed from left to right. For each keyword parameter | specifier, if there is an argument pair whose name matches that | specifier's name (that is, the names are eq), then the parameter | variable for that specifier is bound to the second item (the value) of | that argument pair. If more than one such argument pair matches, the | leftmost argument pair is used. I take that to mean that these results are unambiguously correct: (foo :x 4) => (4 4) (foo :x 4 :x 'no) => (4 4) Of course, it may still be less confusing to disallow duplicate keyword names, as at least one implementations fails this in compiled code (Allegro 7.0): CL-USER(5): (foo :x 4) (4 NIL) -bcd |
From: Stephen W. <wi...@mu...> - 2007-01-03 23:05:37
|
Juho Snellman <js...@ik...> writes: > Stephen Wilson <wi...@mu...> writes: > > Note that, in the interpreter, it is not an error for there to be > > duplicate go tags. I was not aware of this divergent behavior > > before. However, I believe the interpreter has the correct behavior. > > There are other issues tied to this (general binding semantics, > > esp. lambda args/parsing, all ANSI issues), which I can write about > > shortly. > > Ok, it would be good if you could expand a bit on that. I don't quite > understand why signaling an error for duplicate tags in compiled code > but not in interpreted would be the correct behaviour. (Beyond the > "it's undefined, so anything we do is correct" cop out). I meant that duplicate tags should be OK in compiled code, but with a warning emitted instead of an error. Of course, you are right that the behavior is undefined. That being the case, my argument is based on symmetry with other language constructs. Unfortunately, I believe SBCL is currently mishandling some of these so its difficult to use them as a counter example. Let me point out one of the issues. Duplicate parameters in lambdas are not accepted currently. The standard does not forbid duplicate parameters. It specifies the semantics of lambda arg parsing in such a way that it is consistent when duplicate parameters exist. I believe this kind of general specification should be implemented in an equally general way. I dont think the standard intended that lambda args are handled in an implementation dependent manner. I believe we should have the following: (defun foo (a a &key k (k (cons k) k-p)) (list a k k-p)) ==> WARNING: first parameter A not used. (foo 1) ==> ERROR: expected two arguments. (foo 1 2) ==> (2 (nil) nil) (foo 1 2 :k 3) ==> (2 (3) nil) (foo 1 2 :k 3 :k 4) ==> (2 4 t) Of course, my notion of conformance might be a bit whacked. I would like to know what you think. And so, my reasoning that duplicate go tags should be OK is based on agreement with the above and a belief that binding semantics should be applied consistently, even if it isnt `strictly' defined. Thanks, Steve |
From: Stephen W. <wi...@mu...> - 2007-01-03 23:33:34
|
I am wrong here: Stephen Wilson <wi...@mu...> writes: > (foo 1 2 :k 3) ==> (2 (3) nil) > (foo 1 2 :k 3 :k 4) ==> (2 4 t) I forgot the requirement that when duplicate keyword arguments are supplied, the leftmost pair should be taken. This should be: (foo 1 2 :k 3) ==> (2 3 t) (foo 1 2 :k 3 :k 4) ==> (2 3 t) Thanks, Steve |
From: Christophe R. <cs...@ca...> - 2007-01-04 07:22:25
|
Stephen Wilson <wi...@mu...> writes: > I believe we should have the following: > > (defun foo (a a &key k (k (cons k) k-p)) (list a k k-p)) > ==> WARNING: first parameter A not used. Can you provide a reference for this? While I understand that duplicate keyword, optional (and &aux) parameters might be legal, my understanding was that duplicate required parameters were undefined. Cheers, Christophe |
From: Stephen W. <wi...@mu...> - 2007-01-04 22:47:32
|
Christophe Rhodes <cs...@ca...> writes: > > (defun foo (a a &key k (k (cons k) k-p)) (list a k k-p)) > > ==> WARNING: first parameter A not used. > > Can you provide a reference for this? While I understand that > duplicate keyword, optional (and &aux) parameters might be legal, my > understanding was that duplicate required parameters were undefined. >From my understanding, there is nothing in the standard which specifically says duplicate required parameters are undefined, or makes explicit mention that they are not an error. I will see if I can track down a reference, if by chance I missed something. My main point is that if something is undefined in the standard by virtue of it not being given special treatment, then the most general behavior which the standard permits should be the ideal behavior. Would you agree with that on principle? Sincerely, Steve > Cheers, > > Christophe |
From: Christophe R. <cs...@ca...> - 2007-01-04 22:56:43
|
Stephen Wilson <wi...@mu...> writes: > My main point is that if something is undefined in the standard by > virtue of it not being given special treatment, then the most general > behavior which the standard permits should be the ideal behavior. > Would you agree with that on principle? Only if there is a uniquely-defined "most general". In the case of duplicate required parameters, it is not at all clear to me that shadowing the first parameter's binding with the second is more general than shadowing the second's binding with the first, and I don't see any evidence for either being preferred historically. Given this ambiguity, I think it is preferable to signal an error (if that is permitted), to indicate that the code is unreliable. Similarly, in (let ((x 1) (x 2)) x) (where the bindings happen in parallel), I think that signalling an error is better behaviour than returning either 1 or 2. Cheers, Christophe |
From: Stephen W. <wi...@mu...> - 2007-01-04 23:40:02
|
Christophe Rhodes <cs...@ca...> writes: Only if there is a uniquely-defined "most general". In the case of > duplicate required parameters, it is not at all clear to me that > shadowing the first parameter's binding with the second is more > general than shadowing the second's binding with the first, and I > don't see any evidence for either being preferred historically. Given > this ambiguity, I think it is preferable to signal an error (if that > is permitted), to indicate that the code is unreliable. But the standard does say that parameters are processed from left to right (3.4.1.4, 3rd paragraph). As far as convention goes, most lisps do accept duplicate arguments. I think that a warning in this case is appropriate. > Similarly, in > (let ((x 1) (x 2)) x) > (where the bindings happen in parallel), I think that signalling an > error is better behaviour than returning either 1 or 2. The wording in the CLHS uses the term parallel rather loosely. Note that it is different from CLTL2. CLHS: Then all of the variables varj are bound to the corresponding values; CLTL2: Then all of the variables varj are bound to the corresponding values in parallel; If I can convince you on how lambda args are to be treated, then the correspondence below (which certainly does have historical precedent), should govern the behavior of let. ((lambda (a) ...) (foo)) == (let ((a (foo))) ...) I think (let ((x 1) (x 2)) x) == 2. Thanks, Steve |
From: Christophe R. <cs...@ca...> - 2007-01-05 07:27:01
|
Stephen Wilson <wi...@mu...> writes: > Christophe Rhodes <cs...@ca...> writes: > Only if there is a uniquely-defined "most general". In the case of >> duplicate required parameters, it is not at all clear to me that >> shadowing the first parameter's binding with the second is more >> general than shadowing the second's binding with the first, and I >> don't see any evidence for either being preferred historically. Given >> this ambiguity, I think it is preferable to signal an error (if that >> is permitted), to indicate that the code is unreliable. > > But the standard does say that parameters are processed from left to > right (3.4.1.4, 3rd paragraph). It does, but not in a way that convinces that the standard intended to specify the behaviour of duplicate required parameters. > As far as convention goes, most lisps do accept duplicate arguments. See below: they may accept them, but they don't provide useful behaviour, and this I believe strengthens the case for an error. > I think that a warning in this case is appropriate. What useful behaviour would allowing duplicate required parameters enable? >> Similarly, in >> (let ((x 1) (x 2)) x) >> (where the bindings happen in parallel), I think that signalling an >> error is better behaviour than returning either 1 or 2. > > The wording in the CLHS uses the term parallel rather loosely. Note > that it is different from CLTL2. > > CLHS: > Then all of the variables varj are bound to the corresponding > values; I think that the absence of specification of what happens when some of the varj have the same name is telling. > If I can convince you on how lambda args are to be treated, then the > correspondence below (which certainly does have historical precedent), > should govern the behavior of let. > > ((lambda (a) ...) (foo)) == (let ((a (foo))) ...) > > I think (let ((x 1) (x 2)) x) == 2. A quick survey of implementations: CLISP 2.41: * interpreted code: returns 2; * compiled code: returns 1. Allegro 7.0: * interpreted code: returns 1; * compiled code: returns 2. You could argue, of course, that these implementations are simply buggy -- I certainly don't want to argue that either of these behaviours is mandated or useful. However, I think it's fairly clear that this indicates that there is no consensus on your view about LET, at least. Cheers, Christophe |
From: Stephen W. <wi...@mu...> - 2007-01-05 16:20:18
|
Christophe Rhodes <cs...@ca...> writes: > > But the standard does say that parameters are processed from left to > > right (3.4.1.4, 3rd paragraph). > > It does, but not in a way that convinces that the standard intended to > specify the behaviour of duplicate required parameters. But it does specify the beahviour. The spec gives an algorithm (of sorts) for the processing of lambda args which is indifferent to whether duplicate parameters exist or not. Raising an error is far more intrusive behaviour then letting duplicate parameters participate. > What useful behaviour would allowing duplicate required parameters > enable? Yes, its an important question. The fact is that I intuitively agree with you in some ways. I certainly do not like duplicate parameters from a style perspective. I would not use them myself. On the other hand, Lisp is a very robust language. It has been designed not to error out except in truly erroneous situations. I can get past my subjective dislike for duplicate parameters by seeing this as a `robustness' issue. The way in which I deal with complaints raised by a compiler is not too different regarding whether it is a compiler error or warning. Machine generated Lisp code cannot respond to an error. > > The wording in the CLHS uses the term parallel rather loosely. Note > > that it is different from CLTL2. > > > > CLHS: > > Then all of the variables varj are bound to the corresponding > > values; > > I think that the absence of specification of what happens when some of > the varj have the same name is telling. Ya, so do I. Too bad we are getting different messages :) > A quick survey of implementations: > > CLISP 2.41: > * interpreted code: returns 2; > * compiled code: returns 1. > > Allegro 7.0: > * interpreted code: returns 1; > * compiled code: returns 2. > > You could argue, of course, that these implementations are simply > buggy -- I certainly don't want to argue that either of these > behaviours is mandated or useful. However, I think it's fairly clear > that this indicates that there is no consensus on your view about LET, > at least. You mean the correspondence between LET and LAMBDA as I wrote previously? There most certainly is a correspondence. Not just conventionally but theoretically as well via the lambda calculus. Well, theoretically at least, for sure :) I was surprised and disappointed to compare your quick survey above with the following: Given (defun foo (x x) x) and evaluating (foo 1 2) I get 2 in both interpreted and compiled code under both CLISP 2.35 and Allegro 8.0 express edition. I am really amazed that the behavior is not consistent with the implementations of LET. Really, I have yet to see a good argument against my proposal. Please allow me to summarize: i) Processing of actual parameters is well defined when duplicate parameters exist. The number of actual parameters determines how many arguments a call must supply. The parameters themselves are processed from left to right. This is the main technical point of my argument. ii) I do not believe that the standard intended for lambda arg parsing to be implementation dependent, even in special cases. iii) The introduction of an erroneous state in an otherwise well defined situation is not ideal as it leads to a less robust system. A warning is appropriate and more in line with the spirit of the language. iv) The correspondence between LAMBDA arg binding and LET is fundamental, at least theoretically, and therefore should be used as a rule when implementing LET (as it is currently in SBCL). v) For TAGBODY and go tags, the guiding principle should be consistency across `similar' constructs which introduce bindings. To my mind, the behaviour should be: (tagbody (foo) :A (bar) :B (baz) :A (jar)) ==> (tagbody (foo) (bar) :B (baz) :A (jar)) In other words, go tag bindings should be introduced from left to right, where rightmost shadows duplicates on the left, and where an appropriate warning is issued. Thanks a lot for responding to these posts! Sincerely, Steve |
From: Juho S. <js...@ik...> - 2007-01-05 16:54:16
|
Stephen Wilson <wi...@mu...> writes: > v) For TAGBODY and go tags, the guiding principle should be > consistency across `similar' constructs which introduce bindings. To > my mind, the behaviour should be: > > (tagbody (foo) :A (bar) :B (baz) :A (jar)) ==> > (tagbody (foo) (bar) :B (baz) :A (jar)) > > In other words, go tag bindings should be introduced from left to > right, where rightmost shadows duplicates on the left, and where an > appropriate warning is issued. I don't know. That argument seems like a bit of a stretch to me, and it doesn't look like it occured to most implementors of non-SBCL lisps either: (tagbody (print '1) (go :a) :a (print '2) :a (print '3)))) Interpreted Compiled ACL 1 3 compiler error Cmucl infinite loop infinite loop Clisp 1 3 1 2 3 GCL 1 2 3 1 3 ECL 1 3 1 3 Lispworks 1 3 1 2 3 The usual SBCL philosphy with regards to undefined behaviour has been to point it out to the user, rather than guessing at her intentions. Not just to be annoying, but to let the user know that what she's doing might not work properly on other implementations. It seems to me that a compiler error (as in your patch) is the right thing to do. I would also prefer an error in interpreted code too, but can see the argument that it shouldn't be done for efficiency reasons. -- Juho Snellman |
From: Stephen W. <wi...@mu...> - 2007-01-05 18:23:40
|
Juho Snellman <js...@ik...> writes: > The usual SBCL philosphy with regards to undefined behaviour has been > to point it out to the user, rather than guessing at her > intentions. Not just to be annoying, but to let the user know that > what she's doing might not work properly on other implementations. It > seems to me that a compiler error (as in your patch) is the right > thing to do. I would also prefer an error in interpreted code too, but > can see the argument that it shouldn't be done for efficiency reasons. OK. Please see my response to Christophe as well. I am a fan of being consistant. Given that an error is the right thing, I can look into bringing the interpreter in line. I dont think efficiency concerns is enough to have the compiler and interpreter diverge in there behaviour in this case. What do you think about the other related cases on the interpreter side? (let ((x 1) (x 2)) x) == 1 and ((lambda (x x) x) 1 2) == 1 in the interpreter. Given the compiler policy, should we not bring these in line as well? |