Johan Kullstam wrote:
> i was inspired by the fibonacci function which came up in one of the
> threads and decided to write my own.
> here is what i came up with as a first cut (yes, it makes a list of
> length n+2 but i'll fix that eventually...)
> (defun fib-list (n)
> "make a list of fibonacci numbers"
> (let ((acc '(1 1)))
> (dotimes (i n)
> (let ((c (+ (car acc) (cadr acc))))
> (push c acc)))
> (nreverse acc)))
> when i go to run this beast, i get
> > (fib-list 4)
> (1 1 2 3 5 8)
> and think so far so good. but then i run it again and get
> > (fib-list 4)
> (8 5 3 2 1 3 4 7 11)
> what is going on here?
The call to nreverse is destructively modifying not just your data but the
program itself.
Let me repeat that. The '(1 1) is a program constant. By nreversing a list
which has that constant as its tail, you are authorizing your lisp to
destructively modify the cons cells which make up that '(1 1). Next time
you look at those cells, they may legitimately point somewhere else.
So:
> (defun fib-list (n)
"make a list of fibonacci numbers"
(let ((acc '(1 1)))
(dotimes (i n)
(let ((c (+ (car acc) (cadr acc))))
(push c acc)))
(nreverse acc)))
FIB-LIST
> (pprint (function-lambda-expression 'FIB-LIST))
(LAMBDA (N)
(BLOCK FIB-LIST
(LET ((ACC '(1 1)))
(DOTIMES (I N)
(LET ((C (+ (CAR ACC) (CADR ACC)))) (PUSH C ACC)))
(NREVERSE ACC))))
> (FIB-LIST 4)
(1 1 2 3 5 8)
> (pprint (function-lambda-expression 'FIB-LIST))
(LAMBDA (N)
(BLOCK FIB-LIST
(LET ((ACC '(1 2 3 5 8)))
(DOTIMES (I N)
(LET ((C (+ (CAR ACC) (CADR ACC)))) (PUSH C ACC)))
(NREVERSE ACC))))
(Note: exact behaviour of function-lambda-expression can be
implementation-dependent.)
> [.....]
> have i totally misunderstood the meaning of let here? do i have some
> sort of closure situation i am not seeing?
No and no. Just be aware that your let binds acc to some list squirreled
away in memory. Change that list somehow, and you change what acc will
bind to next time you run that code.
> when i swap nreverse for reverse, this whole problem goes away. how
> does nreverse differ from reverse besides trashing its arg? how far
> does its swath of destruction extend?
Purely that nreverse may change the cons cells handed to it, and reverse
may not.
An alternative would be to copy the starting list, so that you are
destructively modifying a list which is not a program literal:
(defun fib-list (n)
"make a list of fibonacci numbers"
(let ((acc (copy-list '(1 1))))
(dotimes (i n)
(let ((c (+ (car acc) (cadr acc))))
(push c acc)))
(nreverse acc)))
> i am using clisp. is this some sort of a clisp bug? i shall try this
> at with ACL later tonight.
No, it looks like clisp did the right thing.
- nick