mps@ntcsd1.UUCP (Michael P. Smith) (11/29/89)
On both a TI and a Sun (using Sun Common Lisp (lucid)), the following occurs: >(DEFUN foo1 () '(a b c)) >FOO1 >(foo1) >(A B C) >(NCONC (foo1) '(d e f)) >(A B C D E F) >(foo1) >(A B C D E F) (DEFUN foo2 () (LIST 'a 'b 'c)) is unaffected in the same situation. I think I understand what is happening. NCONC is treating the pointer re- turned by evaluating (FOO1) just as if it were evaluating a variable FOO1. But I don't like it. There is no variable FOO1, and NCONC is in effect re- defining the function FOO1. I have been using the rule: don't use destructive operations on data struc- tures you care about. Now this turns out to be insufficient. Is there a rule regarding when destructive operations can affect the values returned by functions they are not part of? I'm told that even FOO2 is affected when running Franz on a DEC station. Thanks. Mike Smith ( mps@ntcsd1 | mcnc!ntcsd1!mps )
rshu@zodiac.ADS.COM (Richard Shu) (11/29/89)
In article <464@ntcsd1.UUCP> mps@ntcsd1.UUCP (Michael Smith) writes: > >On both a TI and a Sun (using Sun Common Lisp (lucid)), the following occurs: > > >(DEFUN foo1 () '(a b c)) > >FOO1 > >(foo1) > >(A B C) > >(NCONC (foo1) '(d e f)) > >(A B C D E F) > >(foo1) > >(A B C D E F) > >(DEFUN foo2 () (LIST 'a 'b 'c)) is unaffected in the same situation. Same thing happens on a Symbolics running Genera 7.2 >I think I understand what is happening. NCONC is treating the pointer re- >turned by evaluating (FOO1) just as if it were evaluating a variable FOO1. >But I don't like it. There is no variable FOO1, and NCONC is in effect re- >defining the function FOO1. I'm not an expert at Common Lisp or its implementation but I think your explanation is a little off base. The point is that '(a b c) is a constant and the compiler/interpreter is deciding to treat it that way. Thus, every call of FOO1 returns the SAME copy of '(a b c). This can be shown by evaluating (eq (FOO1) (FOO1)) and seeing that the value returned is T. For comparison (eq (FOO2) (FOO2)) returns NIL. All this is a little weird since (eq '(a b c) '(a b c)) also returns NIL. I understand your confusion but I'll bet somebody can give the "official" explanation why Common Lisp works this way. >I have been using the rule: don't use destructive operations on data struc- >tures you care about. Now this turns out to be insufficient. Actually, the rule is don't use destructive operations on data structures if there might be another pointer (direct or indirect) to the same object and that pointer should not reflect the change that a destructive operation would make. Using this rule requires real thought about the life history of the data structure you're about to change destructively. You have to consider every function that had a chance to reference the data structure since it was created (which means you have to know when it was created). This can be a real pain if data structure's life history is not readily apparent. As a result, I only use destructive operations when the life history is obvious. Typically, I do stuff like (setf (cdr x) y) where x is a cons cell of an alist that is not part of any other alist. You can see why the simplified version of this rule translates to: Don't use destructive operations at all. >Is there a >rule regarding when destructive operations can affect the values returned >by functions they are not part of? I guess there's a corollary that says: make sure you create new copies of lists if that's what you want. Don't use QUOTE (') to cons up data structures. If you don't like LIST, use the BACKQUOTE macro (`). I've been shafted by variations of your bug where the problem was using QUOTE instead of BACKQUOTE. Rich (responsible-p ADS message) NIL (si:halt)
barmar@think.com (11/29/89)
In article <464@ntcsd1.UUCP> mps@ntcsd1.UUCP (Michael Smith) writes: > >(DEFUN foo1 () '(a b c)) > >FOO1 > >(foo1) > >(A B C) > >(NCONC (foo1) '(d e f)) > >(A B C D E F) > >(foo1) > >(A B C D E F) Someone else already explained what is going on -- your NCONC is modifying the actual list that is in your function. Remember, QUOTE returns its argument, *not* a copy of it. >(DEFUN foo2 () (LIST 'a 'b 'c)) is unaffected in the same situation. Since this creates a new list every time it is called, destructive operations cannot affect it. >I have been using the rule: don't use destructive operations on data struc- >tures you care about. Now this turns out to be insufficient. A better rule is: don't return constant data structures from functions, unless the function is documented to return a constant (so the caller knows not to try to modify it). A compiler would be justified in storing constant data structures on read-only pages, so the above NCONC *could* result in a core dump! > Is there a >rule regarding when destructive operations can affect the values returned >by functions they are not part of? If you modify a constant that came from a function, you may be modifying the function. The wording of the rule in the ANSI CL working draft is "the consequences of using a destructive operation on a constant are undefined." > I'm told that even FOO2 is affected when >running Franz on a DEC station. That's a bug in Franz. Sounds like an incorrect compiler optimization to me -- it's translating a call to LIST all of whose arguments are quoted constants into a single quoted constant. Since LIST is supposed to cons a new list each time it is called, this changes the semantics drastically. Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar
cox@Franz.COM (Charles A. Cox) (11/30/89)
In article <31792@news.Think.COM> barmar@think.com writes: In article <464@ntcsd1.UUCP> mps@ntcsd1.UUCP (Michael Smith) writes: > >(DEFUN foo1 () '(a b c)) > >FOO1 > >(foo1) > >(A B C) > >(NCONC (foo1) '(d e f)) > >(A B C D E F) > >(foo1) > >(A B C D E F) [ . . . ] >(DEFUN foo2 () (LIST 'a 'b 'c)) is unaffected in the same situation. > I'm told that even FOO2 is affected when >running Franz on a DEC station. That's a bug in Franz. There's no bug here since Allegro CL is doing the right thing (i.e. what Mr. Smith was told is incorrect). You are correct, though, that had it been the case that foo2 was affected, it would have been a bug. From the Decstation: <cl> (DEFUN foo2 () (LIST 'a 'b 'c)) FOO2 <cl> (NCONC (foo2) '(d e f)) (A B C D E F) <cl> (foo2) (A B C) <cl> (proclaim '(optimize (speed 3) (safety 0))) T <cl> (compile 'foo2) FOO2 <cl> (NCONC (foo2) '(d e f)) (A B C D E F) <cl> (foo2) (A B C) <cl> -- --- Charles A. Cox, Franz Inc. 1995 University Avenue, Suite 275 Internet: cox@franz.com Berkeley, CA 94704 uucp: uunet!franz!cox Phone: (415) 548-3600 FAX: (415) 548-8253
pf@islington-terrace.csc.ti.com (Paul Fuqua) (11/30/89)
Date: Tuesday, November 28, 1989 9:56pm (CST) From: barmar at think.com Subject: Re: NCONC & Functions Newsgroups: comp.lang.lisp In article <464@ntcsd1.UUCP> mps@ntcsd1.UUCP (Michael Smith) writes: > >(DEFUN foo1 () '(a b c)) > >(NCONC (foo1) '(d e f)) A compiler would be justified in storing constant data structures on read-only pages, so the above NCONC *could* result in a core dump! In fact, the Explorer compiler will put such constants into read-only areas when compiling a file (just compiling a function won't do it). For some reason, it causes lots of user confusion, so around Release 3 we modified the message for write-into-read-only errors to add the phrase "the code was probably trying to modify a program constant" when appropriate. Paul Fuqua pf@csc.ti.com {smu,texsun,cs.utexas.edu,rice}!ti-csl!pf Texas Instruments Computer Science Center PO Box 655474 MS 238, Dallas, Texas 75265
HEP@smectos.gang.umass.edu (High Energy Physics Group) (11/30/89)
With some text deleted, >> >(DEFUN foo1 () '(a b c)) >> >FOO1 >> >(foo1) >> >(A B C) >> >(NCONC (foo1) '(d e f)) >> >(A B C D E F) >> >(foo1) >> >(A B C D E F) >> I'm told that even FOO2 is affected when >>running Franz on a DEC station. > >That's a bug in Franz. Sounds like an incorrect compiler optimization to >me -- it's translating a call to LIST all of whose arguments are quoted >constants into a single quoted constant. Since LIST is supposed to cons a >new list each time it is called, this changes the semantics drastically. I have a copy of Franz's Allegro CL on my DECstation 3100, and this is what happens: dribbling to file "/usr/users/kgk/junk" NIL <cl> (defun foo2 () (list 'a 'b 'c)) FOO2 <cl> (nconc (foo2) '(it works fine)) (A B C IT WORKS FINE) <cl> (foo2) (A B C) <cl> (defun foo2 () (declare (optimize (speed 3) (safety 0))) (list 'a 'b 'c)) FOO2 <cl> (compile 'foo2) FOO2 <cl> (nconc (foo2) '(it works fine)) (A B C IT WORKS FINE) <cl> (foo2) (A B C) <cl> (dribble) As far as I can see, Allegro CL does not seem to be in error! :-) -- Kleanthes
rar@KRONOS.ADS.COM (Bob Riemenschneider) (11/30/89)
=> From: mps@ntcsd1.UUCP (Michael P. Smith) => => On both a TI and a Sun (using Sun Common Lisp (lucid)), the following occurs: => => >(DEFUN foo1 () '(a b c)) => >FOO1 => >(foo1) => >(A B C) => >(NCONC (foo1) '(d e f)) => >(A B C D E F) => >(foo1) => >(A B C D E F) This seems to me to be just what you should have expected. You stuck a particular list structure in FOO1's function cell and destructively modified that list structure. Once you start using destructive operations, you have to abandon some abstraction and think about what's really going on in memory. That is, NCONC drags DEFUN down to it's own level. => (DEFUN foo2 () (LIST 'a 'b 'c)) is unaffected in the same situation. Again, this is reasonable. If you stick (LIST 'A 'B 'C) in the function cell, every call CONSes up a new list. => I have been using the rule: don't use destructive operations on data => structures you care about. Well, that may be a good rule, depending on what you mean by "care about". The rule I try to use is: don't use destructive operations unless code profiling shows you really need them in some function; then insert them, think carefully about whether any other changes are required to preserve correctness, and test the modified system carefully just in case you made a mistake. => I'm told that even FOO2 is affected when running Franz on a DEC station. I suppose one could argue that this is a legal optimization since (LIST 'A 'B 'C) and '(A B C) are clearly equivalent in some sense. But there are lots of different senses of equivalence, especially when you have destructive operations: see Ian Mason's _The Semantics of Destructive Lisp_. (Note that I couldn't reproduce this in the Franz on my Sun3, even compiling, even with a proclamation to crank optimization up all the way.) One last note: a lot of Lispers think of this behavior as a feature rather than a bug. Consider the following program for computing Fibonacci numbers, based on an example in Talcott and McCarthy's Lisp course notes: (defun fibon (n) (if (or (eq n 0) (eq n 1)) 1 (fibonaux n '(1 1)))) (defun fibonaux (n l) (if (null (cddr l)) (if (eq n 2) (cadr (rplacd (cdr l) (list (+ (car l) (cadr l))))) (fibonaux (- n 1) (rplacd (cdr l) (list (+ (car l) (cadr l)))))) (if (eq n 2) (caddr l) (fibonaux (- n 1) (cdr l))))) This function modifies it's own definition at run-time, making itself "smarter" by caching newly computed Fibonacci numbers. Mason says Although this example is trivial, it elegantly illustrates why Lisp is *the* language for artificial intelligence. Personally, I agree with John Allen's assessment: This technique is similar to the machine language tricks of self-modifying code and should be used with similar care. The freedom to hang yourself should not be construed as an invitation to do so, but it again points out the similarities of LISP to machine language and highlights the differences between LISP and contemporary high-level languages. When I recently taught a Lisp short course, I showed the example -- which is why I had all this stuff handy -- and warned against doing such things. It's nice to know you *can* even though you probably shouldn't. -- rar
mps@ntcsd1.UUCP (Michael P. Smith) (11/30/89)
In article <464@ntcsd1.UUCP> mps@ntcsd1.UUCP (Michael Smith) writes: > >On both a TI and a Sun (using Sun Common Lisp (lucid)), the following occurs: > > >(DEFUN foo1 () '(a b c)) > >FOO1 > >(foo1) > >(A B C) > >(NCONC (foo1) '(d e f)) > >(A B C D E F) > >(foo1) > >(A B C D E F) > >(DEFUN foo2 () (LIST 'a 'b 'c)) is unaffected in the same situation. > >by functions they are not part of? I'm told that even FOO2 is affected when >running Franz on a DEC station. Now I'm told by the same source that FOO2 is NOT affected in Franz. I apologize posting something I hadn't ascertained myself. BTW, thanks to some early responses I'm a little clearer about what bothers me about this. If FOO1 had referred to a variable whose value was changed as a result of NCONCing the result of calling FOO1 with something else, I would not have been surprised. I was surprised to find that even constants can be modified (just not re-assigned or bound), but I can live with it. I'm less happy that some apparently read p. 86 of Steele (1st ed.) to imply that quoted expressions are just like lisp constants in this respect (i.e., modifi- able). But what really bothers me is this: (SYMBOL-FUNCTION foo1) evaluated before the NCONC yields: (NAMED-LAMBDA FOO1 NIL (BLOCK FOO1 '(A B C))) After the NCONC, we get: (NAMED-LAMBDA FOO1 NIL (BLOCK FOO1 '(A B C D E F))). These look like different functions to me. (DEFUN foo3 () *glorp*), where *glorp* is a lisp constant assigned '(A B C) will exhibit the same behavior. But at least its function definition remains unchanged. Mike Smith ( mps@ntcsd1 | mcnc!ntcsd1!mps ) <The views expressed above are fictional. Any resemblance to real facts, whether living or dead, is purely coincidental.>
doug@zodiac.ADS.COM (Doug Morgan) (11/30/89)
When the lisp reader sees: (DEFUN foo1 () '(a b c)) it turns the text (a b c) into an internal representation as a list of conses pointing to symbols. At run time the ' (actually (quote ...)) returns THAT PARTICULAR internal representation. A later NCONC munches that representation and you get exactly what you saw. In the case of (DEFUN foo2 () (LIST 'a 'b 'c)), the reader creates a different internal representation. This one is 4 conses with the first pointing to the symbol named LIST. At run time, the evaluation of this representation causes a 3 cons list pointing to a b and c to be generated. Since these lists are generated upon each evaluation of (LIST ...), NCONC can't affect later ones. Again exactly as you saw. Any lisp that treats FOO2 like FOO1 is in error, no ifs ands or buts. I use destructive operations pretty much at will on lists that are created from nil and the operations are in the lexical scope of the point of creation (I get to look at everything that could be building pointers to such lists). Never (well, hardly ever) trust a list that's passed in from "outside." doug
barmar@think.com (11/30/89)
In article <465@ntcsd1.UUCP> mps@ntcsd1.UUCP (Michael Smith) writes: > But what really bothers me is this: (SYMBOL-FUNCTION foo1) evaluated >before the NCONC yields: > (NAMED-LAMBDA FOO1 NIL > (BLOCK FOO1 > '(A B C))) >After the NCONC, we get: > (NAMED-LAMBDA FOO1 NIL > (BLOCK FOO1 > '(A B C D E F))). >These look like different functions to me. Just because they look different doesn't mean they *are* different. That's the problem with destructive operations. Consider: (setq *foo* (list 1 2 3)) *foo* => (1 2 3) (nconc *foo* '(a b c)) *foo* => (1 2 3 A B C) The two values of *FOO* look different, but they are the same Lisp object. One of the fundamental concepts of Lisp is that programs and data are made up of the same "stuff". Therefore, when you NCONC a list that is in a program, you are modifying the program itself. By the way, Lisp is *not* unique in this regard. In C you are not permitted to modify a string that was created as a result of a literal string constant in the program. In implementations that don't put them on a read-only page you're likely to change the copy in the text section of the program. Consider the following program excerpt: func(x) int x; { char *string = "abcd"; if (x) string[0] = 'q'; printf ("%s ", string); } main() { func(0); func(1); func(0); } I believe that this will print out "abcd qbcd qbcd" in many implementations, because the string constant is modified during the second call. Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar
hoey@ai.etl.army.mil (Dan Hoey) (11/30/89)
In article <8911292103.AA21810@kronos.ads.com> rar@KRONOS.ADS.COM (Bob Riemenschneider) writes: >One last note: a lot of Lispers think of this behavior [modifying quoted >structure in a function definition] as a feature rather >than a bug. Consider the following program .... I used to think so, too, but the designers of Common Lisp have decided that modifying quoted structure is an error. The rationale is to allow two features that they thought outweighed the utility of this technique: 1. The compiler is free to collapse all EQUAL quoted structures into one, saving some amount of space. I do not know if this is implemented by any compiler. 2. The system is free to put quoted structure into write-protected memory, and thus signal an error if the user accidentally modifies the structure. I am somewhat uncomfortable with this, since it also prevents the user from purposefully modifying the structure, but since some lisp machine manufacturers have implemented this feature, I don't think it's worth debating. The sort of work-around that I believe is approved is (1) for now, use a special variable, and (2) when enough lisps support it, go to DEFUNs with a lexical environment, such as (borrowing Bob's code) + (let ((aux (list 1 1))) (defun fibon (n) (if (or (eq n 0) (eq n 1)) 1 ! (fibonaux n aux)))) Dan
jeff@aiai.ed.ac.uk (Jeff Dalton) (01/09/90)
In article <373@ai.etl.army.mil> hoey@ai.etl.army.mil (Dan Hoey) writes: >In article <8911292103.AA21810@kronos.ads.com> rar@KRONOS.ADS.COM (Bob Riemenschneider) writes: >>One last note: a lot of Lispers think of this behavior [modifying quoted >>structure in a function definition] as a feature rather than a bug. > >I used to think so, too, but the designers of Common Lisp have decided that >modifying quoted structure is an error. It's not just Common Lisp. There were other Lisps (eg, Franz Lisp) in which modifying quoting constants might go wrong in compiled code. -- Jeff