10

I've written the following bit of code to simulate rolling a six-sided die a number of times and counting how many times each side landed up:

(defun dice (num)
  (let ((myList '(0 0 0 0 0 0)))
    (progn (format t "~a" myList)
           (loop for i from 1 to num do
                 (let ((myRand (random 6)))
                   (setf (nth myRand myList) (+ 1 (nth myRand myList)))))
           (format t "~a" myList))))

The function works great the first time I call it, but on subsequent calls the variable myList starts out at the value it had at the end of the last call, instead of being initialized back to all zeros like I thought should happen with the let statement. Why does this happen?

Dustin
  • 103
  • 3
  • 1
    FYI, most people writing common lisp these days use names like `my-list` rather than `myList`. Also, I don't believe you need that progn because "the body of a let is an implicit progn" http://www.lispworks.com/documentation/HyperSpec/Body/s_let_l.htm (it's like the progn is already there). – Samuel Edwin Ward Feb 25 '12 at 18:16

3 Answers3

13

This is a result of using a constant list in the initializer:

(let ((myList '(0 0 0 0 0 0)))

Change that line to:

(let ((myList (list 0 0 0 0 0 0)))

and it will behave as you expect. The first line only results in an allocation once (since it's a constant list), but by calling list you force the allocation to occur every time the function is entered.

edit: This may be helpful, especially towards the end. Successful Lisp

The answer to this question may also be helpful.

This uses the loop keyword collecting which collects the results of each iteration into a list and returns the list as the value of the loop.

Community
  • 1
  • 1
asm
  • 8,758
  • 3
  • 27
  • 48
  • Thanks Andrew and Michael. That did the trick. I am new to Lisp (that is probably obvious) and I will check out the text that you linked to. – Dustin Sep 01 '11 at 20:59
  • No problem, Lisp is a very fun language to learn :) As Michael pointed out SBCL can be very helpful, it's very aggressive about warning of possibly unsafe operations. – asm Sep 01 '11 at 21:22
  • 1
    Also, typically, using an array instead of a list when you intend to repeatedly index it by position and not frequently grow it is a Good Thing. – Vatine Sep 02 '11 at 13:33
  • That's an excellent point, @Dustin, `nth` is a linear time operation because it must iterate through the list to the selected index. – asm Sep 02 '11 at 16:03
  • The last example does something completely different... it just returns a list of n random numbers 0-5 instead of a list of six counters for the number of occurrence of each of the six faces. – 6502 Sep 08 '11 at 16:45
8

SBCL tells you what's wrong:

* (defun dice (num)
  (let ((myList '(0 0 0 0 0 0)))
    (progn (format t "~a" myList)
           (loop for i from 1 to num do
                 (let ((myRand (random 6)))
                   (setf (nth myRand myList) (+ 1 (nth myRand myList)))))
           (format t "~a" myList))))
; in: DEFUN DICE
;     (SETF (NTH MYRAND MYLIST) (+ 1 (NTH MYRAND MYLIST)))
; ==>
;   (SB-KERNEL:%SETNTH MYRAND MYLIST (+ 1 (NTH MYRAND MYLIST)))
; 
; 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

DICE

So in essence: Don't call destructive functions (here setf) on constant data.

Michael Markert
  • 3,986
  • 2
  • 19
  • 19
-1

like the post above, the compiler allocates the 0 as constant space. I used to know some tricks for this, one would be make it a macro such:

`',(list 0 0 0 0 0)
=>
 ?? (I forget and don't have the other machine on to check)

or wrapped in an (eval-when (compile)) ... )

also

 (list 0 0 0 0 0) 
=>
  #.(list 0 0 0 0)

I don't know if this still works (or ever worked). The are also some implentation macros or compiler macros that could help keep the alloaction size constant but the data variable. Don't rememeber off the top of my head anymore.

remeber to use fill (like bzero in c).

Didier Ghys
  • 30,396
  • 9
  • 75
  • 81
steve
  • 57
  • 1
  • 3