Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Unintentional static variable

37 views
Skip to first unread message

Dom De Felice

unread,
Feb 5, 2011, 3:48:25 PM2/5/11
to
Hello,
I really can't understand what is happening here. This is the code:

(defun prime-points-of-card (card)
(let ((prime-points '((7 21) (6 18) (1 16) (5 15) (4 14) (3 13) (2
12) (10 10) (9 10) (8 10))))
(cadr (assoc (card-rank card) prime-points))))

(defun prime-points (deck)
(let ((scores '((denari 0) (coppe 0) (bastoni 0) (spade 0))))
(print scores)
(loop for card across deck do
(let ((suit (card-suit card)))
(setf (cadr (assoc suit scores)) (max (cadr (assoc suit
scores))
(prime-points-of-card
card)))))
(print scores)
(apply #'+ (mapcar #'cadr scores))))


The `print's are there for debugging purpose.
What I want is:
* scores value is '((denari 0) (coppe 0) (bastoni 0) (spade 0))
everytime I call prime-points
What happens is:
* scores keeps the value from the previous call to prime-points

How is that possible? Shouldn't "let" create a new environment that
vanishes as soon as the function returns?


[3]> (prime-points #())

((DENARI 0) (COPPE 0) (BASTONI 0) (SPADE 0))
((DENARI 0) (COPPE 0) (BASTONI 0) (SPADE 0))
0
[4]> (prime-points #())

((DENARI 0) (COPPE 0) (BASTONI 0) (SPADE 0))
((DENARI 0) (COPPE 0) (BASTONI 0) (SPADE 0))
0
[5]> (prime-points (deck 'stock))

((DENARI 0) (COPPE 0) (BASTONI 0) (SPADE 0))
((DENARI 21) (COPPE 21) (BASTONI 21) (SPADE 21))
84
[6]> (prime-points #())

((DENARI 21) (COPPE 21) (BASTONI 21) (SPADE 21))
((DENARI 21) (COPPE 21) (BASTONI 21) (SPADE 21))
84


This seems crazy to me.

I'm using GNU CLISP. Maybe a bug of CLISP?

Thanks,
Dom

Zach Beane

unread,
Feb 5, 2011, 3:59:39 PM2/5/11
to
Dom De Felice <dom.f...@gmail.com> writes:

[snip modification of quoted lists]


> What happens is:
> * scores keeps the value from the previous call to prime-points

Don't modify quoted lists. Create the data freshly with calls to
something like LIST or CONS, or copy the quoted data with something like
COPY-TREE.

Zach

Rainer Joswig

unread,
Feb 5, 2011, 4:03:15 PM2/5/11
to
In article
<aad9b100-d32e-461f...@r4g2000prm.googlegroups.com>,

Dom De Felice <dom.f...@gmail.com> wrote:

> Hello,
> I really can't understand what is happening here. This is the code:
>
> (defun prime-points-of-card (card)
> (let ((prime-points '((7 21) (6 18) (1 16) (5 15) (4 14) (3 13) (2
> 12) (10 10) (9 10) (8 10))))
> (cadr (assoc (card-rank card) prime-points))))
>
> (defun prime-points (deck)
> (let ((scores '((denari 0) (coppe 0) (bastoni 0) (spade 0))))
> (print scores)
> (loop for card across deck do
> (let ((suit (card-suit card)))
> (setf (cadr (assoc suit scores)) (max (cadr (assoc suit
> scores))
> (prime-points-of-card
> card)))))
> (print scores)
> (apply #'+ (mapcar #'cadr scores))))

Don't do that.

SCORES is a literal list. Data inside a procedure.
Modifying has undefined consequences in Common Lisp.
Imagine for example a compiler which stores
literal data in read-only memory.

You are modifying the literal list in the (SETF (CADR ...))
part.

Then a compiler will store that literal list only
once in the code. So you are modifying this
one literal data structure.

If you want a fresh new list each time you
call that function, you need to create that
list with the usual operations (LIST, CONS, ...)
or copy a another list using one of COPY-LIST
or COPY-TREE. For your purpose there is COPY-TREE.

You might also want to think about a solution
where you don't need to modify the datastructure.

No, see above.
>
> Thanks,
> Dom

Dom De Felice

unread,
Feb 5, 2011, 4:56:42 PM2/5/11
to
On 5 Feb, 22:03, Rainer Joswig <jos...@lisp.de> wrote:
> In article
> <aad9b100-d32e-461f-acd0-ac0397088...@r4g2000prm.googlegroups.com>,

Thanks for the replies, I solved the problem.

Anyway.. don't you think this behaviour is ill conceived? I think it
violates the principle of least surprise: am I the only one to expect
let to act always the same? If I say I want to bind foo to '(1 2 3) I
would expect to always have '(1 2 3) as value for foo. From my point
of view quoting a literal list is just a useful/faster way to create a
list and hasn't the implicit meaning that the list won't be modified.
This thing forces me to see and comprehend the under-the-hood
behaviours of the compiler. Am I missing something?

Thanks for bearing with me :-)

jos...@lisp.de

unread,
Feb 5, 2011, 5:11:37 PM2/5/11
to

I don't think that principle was guiding the design of CL. ;-)

> am I the only one to expect
> let to act always the same? If I say I want to bind foo to '(1 2 3) I
> would expect to always have '(1 2 3) as value for foo.

Even if you modify it you would expect it to stay the same?
If Lisp had to allocate memory for constant data
on each call, it would be less efficient.

Personally it would surprise me if it were different.

> From my point
> of view quoting a literal list is just a useful/faster way to create a
> list and hasn't the implicit meaning that the list won't be modified.

A literal list denotes data, there is no creation
involved. LIST creates a list.

This principle is not special to lists. It is the same for other types
of data. For examples arrays/vectors/strings and structures.

#(1 2 3) is a literal vector.

(vector 1 2 3) creates a new vector.

> This thing forces me to see and comprehend the under-the-hood
> behaviours of the compiler. Am I missing something?

You don't need to understand the compiler.
It is part of the specification.
The compiler writer has to implement the spec
and got some degrees of freedom.

Another example:

---
(defvar *foo* '(1 2 3))

(defvar *bar* '(1 2 3))
---

If above is in a Lisp file and the file compiler, using the
function COMPILE-FILE, compiles that file, then the compiler
can create code such that only one list is allocated later
and it is shared by both variables. Both variables
would then point to the same physical list.

The CL spec says that this is an allowed optimization
for space.

Pascal J. Bourguignon

unread,
Feb 5, 2011, 6:03:54 PM2/5/11
to
Dom De Felice <dom.f...@gmail.com> writes:

> Anyway.. don't you think this behaviour is ill conceived? I think it
> violates the principle of least surprise: am I the only one to expect
> let to act always the same? If I say I want to bind foo to '(1 2 3) I
> would expect to always have '(1 2 3) as value for foo.

> From my point of view quoting a literal list is just a useful/faster
> way to create a list and hasn't the implicit meaning that the list
> won't be modified.

That's your error.

However, if you want a "useful/faster" way to create lists, you can do
it with a reader macro.

(defun useful/faster-create-list-reader-macro (stream ch)
(declare (ignore ch))
(let ((list (read stream)))
`(list ,@(mapcar (lambda (item) `(quote ,item)) list))))

Simple enough isn't it? Now just hook it up to some character:

(set-macro-character #\! 'useful/faster-create-list-reader-macro)

CL-USER> !(a b c)
(A B C)
CL-USER> (eq !(a b c) !(a b c))
NIL
CL-USER> (loop
:repeat 5
:for list = !(value = 0)
:do (incf (third list)) (print list))

(VALUE = 1)
(VALUE = 1)
(VALUE = 1)
(VALUE = 1)
(VALUE = 1)
NIL
CL-USER>


And for those who would want to mention `, it does not ensure a fresh
list. Even when you have comas, it can share literal parts.

> This thing forces me to see and comprehend the under-the-hood
> behaviours of the compiler. Am I missing something?

Yes. This has nothing to do with the behavior of the compiler. In
particular, it's probable that a compiler would generate the quoted
literals in read only memory, and therefore that the behavior on
compiled code would be quite different.

On the contrary, it has all to do with the defintion of the language,
and the fact that YOU MUST NOT MODIFY LITERALS!

The same is true in all programming languages.


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.

Tim X

unread,
Feb 5, 2011, 6:33:37 PM2/5/11
to
Dom De Felice <dom.f...@gmail.com> writes:

No, your mistake is to believe that '(1 2 3) is the same as (list i 2
3). The quoted form is NOT just a convenience faeture. Think of it like
a constant literal and don't modify it. Either use one of the list
copying functions to create a new copy and operate on that or use one of
the list construction functions to create it rather than using quote.

Tim

--
tcross (at) rapttech dot com dot au

Edmunds Cers

unread,
Feb 5, 2011, 6:35:11 PM2/5/11
to
"Pascal J. Bourguignon" <p...@informatimago.com> writes:

> On the contrary, it has all to do with the defintion of the language,
> and the fact that YOU MUST NOT MODIFY LITERALS!
>
> The same is true in all programming languages.

But it might be invisible/hidden in other languages. Consider the
following C expression:

char str[] = "literal";

For somebody new to Lisp

(let ((a "literal")) ...

might seem to do the same thing. I actually find this difference quite
intricate and would agree that Lisps behavior is not intuitive for
somebody having a background in more "conventional" languages.

--
A change in perspective is worth 80 IQ points. --- Alan Kay

Pascal J. Bourguignon

unread,
Feb 5, 2011, 7:14:40 PM2/5/11
to
Edmunds Cers <edm...@laivas.lv> writes:

What are you saying?
In C you get the exact same implementation dependant behaviors.

#include <stdio.h>
char* f(){
char* a="hello";
a[0]++;
return(a);
}
int main(){
int i;
for(i=0;i<3;i++){
printf("%s\n",f());
}
return(0);
}

Some C implementations will produce like the clisp interpreter:

iello
jello
kello

some other C implementation will SIGSEGV, or report an error:


[pjb@kuiper :0.0 ~]$ gcc -o f f.c && ./f
Segmentation fault

EiC 3> for(i=0;i<3;i++){ printf("%s\n",f()); }
Error in ::EiC:: near line 3: Illegal cast operation
Error in ::EiC:: near line 3: Undeclared identifier i
Warning: in ::EiC:: near line 3: Possible non relational operation
Error in ::EiC:: near line 3: Cannot apply inc\dec
Error in ::EiC:: near line 3: Undefined type for relational operation
Error: clean up forced

Elias Mårtenson

unread,
Feb 5, 2011, 10:51:58 PM2/5/11
to
It might also be good to point out that SBCL flags this as an error:

CL-USER> (let ((a '(a b c))) (setf (nth 1 a) 'foo) a)
; in: LAMBDA NIL
; (SETF (NTH 1 A) 'FOO)
; ==>
; (SB-KERNEL:%SETNTH 1 A 'FOO)
;
; caught WARNING:
; Destructive function SB-KERNEL:%SETNTH called on constant data.
; See also:
; The ANSI Standard, Special Operator QUOTE
; The ANSI Standard, Section 3.2.2.3
;
; compilation unit finished
; caught 1 WARNING condition
(A FOO C)

Dom De Felice

unread,
Feb 6, 2011, 2:28:46 AM2/6/11
to
Thanks for all the explanations, I have all clear now!
This NG is amazing :-)

dom

Edmunds Cers

unread,
Feb 6, 2011, 3:01:18 AM2/6/11
to
"Pascal J. Bourguignon" <p...@informatimago.com> writes:

> What are you saying?
> In C you get the exact same implementation dependant behaviors.

You are right, of course. Brain fog, sorry (should not post late).

Dom De Felice

unread,
Feb 6, 2011, 4:39:28 AM2/6/11
to
I think the confusion for people coming from other languages is to
think

(let ((string "literal")) ... )

to be the same as

char string[] = "literal"

while it is not, it is the same as:

char* string = "literal";


This shows the difference:


#include <stdio.h>

void foo() {
char string[] = "Fresh new at every call";

printf("%s\n", string);
string[0]++;
printf("%s\n", string);
}

void bar() {
char* string = "I am in read-only memory";

printf("%s\n", string);
string[0]++; // Segmentation fault
}

int main(int argc, char **argv) {
foo();
foo();
foo();

bar();
bar();
bar();

return 0;
}


dom@dom-laptop:~/Progetti/c$ gcc -o test test.c && ./test
Fresh new at every call
Gresh new at every call
Fresh new at every call
Gresh new at every call
Fresh new at every call
Gresh new at every call
I am in read-only memory
Errore di segmentazione
dom@dom-laptop:~/Progetti/c$


dom

Tim Bradshaw

unread,
Feb 6, 2011, 5:00:57 AM2/6/11
to
On 2011-02-05 21:56:42 +0000, Dom De Felice said:

> From my point
> of view quoting a literal list is just a useful/faster way to create a
> list and hasn't the implicit meaning that the list won't be modified.
> This thing forces me to see and comprehend the under-the-hood
> behaviours of the compiler. Am I missing something?

Apart from anything else, let's say that datastructure is large. Your
approach would require the system to allocate a fresh copy of it on
each call, and would provide no mechanism for avoiding that. Instead,
CL provides a mechanism for avoiding that (a literal), and allows you
easily to get that behaviour of you should want it (make a copy).

This behavious is common to many programming languages.

Paul Rubin

unread,
Feb 6, 2011, 5:24:04 AM2/6/11
to
Dom De Felice <dom.f...@gmail.com> writes:
> would expect to always have '(1 2 3) as value for foo. From my point
> of view quoting a literal list is just a useful/faster way to create a
> list and hasn't the implicit meaning that the list won't be modified.
> This thing forces me to see and comprehend the under-the-hood
> behaviours of the compiler. Am I missing something?

Mutating the contents of any list, literal or not, always seems a bit
smelly to me. Maybe you want some other data structure such as a hash
or some sort of persistent tree.

George Neuner

unread,
Feb 17, 2011, 7:22:41 PM2/17/11
to

GCC (by default) is non-compliant. If you want spec compliant
behavior you need to specify "-ansi" and occasionally also
"-pedantic".

The C spec guarantees that non-const qualified literal arrays are
modifiable. However, in the case of a string literal, the result of
modification is unspecified because compilers are permitted to combine
identical string literals regardless of const qualification.

Also, the storage type of a non-const literal defined within a
function may be either "static" or "automatic" (see 6.7.2 and 6.7.3).
Thus your example is invoking undefined behavior by returning a
pointer to the function scoped literal.

George

0 new messages