Accessing call stack depth in Scheme - functional-programming

In order to demonstrate the effectiveness of tail recursion, I would like a way to access the depth of the call stack dynamically in Scheme.
Is there a way to do this? If not, is there a way to do this in other major functional languages (OCaml, Haskell, etc.)?

Racket allows you to store values in the call stack.
You can use this to keep track of the depth.
Here is how I would do it:
#lang racket
;;; This module holds the tools for keeping track of
;;; the current depth.
(module depth racket
(provide (rename-out [depth-app #%app])
current-depth)
(define (extract-current-continuation-marks key)
(continuation-mark-set->list (current-continuation-marks) key))
(define (current-depth)
(car (extract-current-continuation-marks 'depth)))
(define-syntax (depth-app stx)
(syntax-case stx ()
[(_depth-app proc args ...)
#'(let ([p (with-continuation-mark 'depth (+ (current-depth) 1)
proc)]
[as (with-continuation-mark 'depth (+ (current-depth) 1)
(list args ...))])
(apply p as))])))
;;; Importing the #%app from the depth module will override
;;; the standard application to use depth-app which keeps
;;; track of the depth.
(require 'depth)
(with-continuation-mark 'depth 0 ; set the initial depth to 0
(let ()
;;; Example: foo is tail recursive
(define (foo n)
(displayln (list 'foo n (current-depth)))
(if (< n 5)
(foo (+ n 1))
'done))
;;; bar is not tail recursive
(define (bar n)
(displayln (list 'bar n (current-depth)))
(if (< n 5)
(cons n (bar (+ n 1)))
'()))
;;; Call the examples
(foo 0)
(bar 0)))
The output is:
(foo 0 2)
(foo 1 2)
(foo 2 2)
(foo 3 2)
(foo 4 2)
(foo 5 2)
(bar 0 2)
(bar 1 3)
(bar 2 4)
(bar 3 5)
(bar 4 6)
(bar 5 7)
'(0 1 2 3 4)
The output shows that the depth doesn't increase in foo and that it does in bar.

There is no standard way of doing it.
Tail call optimization == no call stack increase. You demonstrate it by writing what normally would blow the stack and run it.
You might get a short stack trace when signalling an error deep in, but how it looks like is implentation specific.
In CL you can trace functions and you'll see no output on consecutive tail calls when it's tail call optimized.
Lazy languages do not need tail recursion since evaluation is by need.

Related

Can the way in which a function is called depend on its arguments?

In Common Lisp, is there a way for an argument to a function to determine how the function is called, in the following sense? Let's say we have a function which has alredy been defined, say (defun foo (n) (+ 3 n)) and we want to define an iterative calls form ic which works in the following way:
(foo 6) => 9
(foo (ic 3 6)) => (foo (foo (foo 6))) => 15
(foo (ic 4 6)) => (foo (foo (foo (foo 6)))) => 18
Can this be done without redefining the function foo? Clearly ic needs to influence a function call outside itself.
By default no. That will change the semantics of the language: It will change what programs mean in the language. That said, you can define macros with such features but then, that will a domain specific language.
Macros are the designated tool for situations where you want to create forms with a different evaluation order from standard procedures.
To achieve the iterated function you want, you can simply define a function which takes a function func and an integer n then, returns a function which applies func n times to its arguments.
(defun iterate-function (func n)
"return a function which applies func n times to its argument.
(funcall (ic f 3) 0) => (f (f (f 0)))"
(unless (and (plusp n) (integerp n))
(error "n must be a non-negative integer"))
(let ((fns (make-list (1- n) :initial-element func)))
#'(lambda (&rest args)
(reduce #'funcall fns :initial-value (apply func args)))))
Now, we can create an iterated function like so:
CL-USER> (ic #'(λ (x) (* x x)) 3)
#<FUNCTION (LAMBDA (&REST ARGS) :IN IC) {100A67B6DB}>
We can now apply the iterated function to arguments like so:
CL-USER> (funcall (ic #'(λ (x) (* x x)) 3) 2)
256
One, possibly complex, way would be to define a macro BAR which would rewrite code.
Source:
(bar (foo (ic 3 6)))
Rewrite:
(foo (foo (foo 6)))
The macro BAR might need a code walker to transform more complex Lisp code like:
(bar
(let ((arg 6))
(foo (ic 3 arg))))

Recursion in Common Lisp, pushing values, and the Fibonacci Sequence

This is not a homework assignment. In the following code:
(defparameter nums '())
(defun fib (number)
(if (< number 2)
number
(push (+ (fib (- number 1)) (fib (- number 2))) nums))
return nums)
(format t "~a " (fib 100))
Since I am quite inexperienced with Common Lisp, I am at a loss as to why the function does not return an value. I am a trying to print first 'n' values, e.g., 100, of the Fibonacci Sequence.
Thank you.
An obvious approach to computing fibonacci numbers is this:
(defun fib (n)
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
(defun fibs (n)
(loop for i from 1 below n
collect (fib i)))
A little thought should tell you why no approach like this is going to help you compute the first 100 Fibonacci numbers: the time taken to compute (fib n) is equal to or a little more than the time taken to compute (fib (- n 1)) plus the time taken to compute (fib (- n 2)): this is exponential (see this stack overflow answer).
A good solution to this is memoization: the calculation of (fib n) repeats subcalculations a huge number of times, and if we can just remember the answer we computed last time we can avoid doing so again.
(An earlier version of this answer has an overcomplex macro here: something like that may be useful in general but is not needed here.)
Here is how you can memoize fib:
(defun fib (n)
(check-type n (integer 0) "natural number")
(let ((so-far '((2 . 1) (1 . 1) (0 . 0))))
(labels ((fibber (m)
(when (> m (car (first so-far)))
(push (cons m (+ (fibber (- m 1))
(fibber (- m 2))))
so-far))
(cdr (assoc m so-far))))
(fibber n))))
This keeps a table – an alist – of the results it has computed so far, and uses this to avoid recomputation.
With this memoized version of the function:
> (time (fib 1000))
Timing the evaluation of (fib 1000)
User time = 0.000
System time = 0.000
Elapsed time = 0.000
Allocation = 101944 bytes
0 Page faults
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875
The above definition uses a fresh cache for each call to fib: this is fine, because the local function, fibber does reuse the cache. But you can do better than this by putting the cache outside the function altogether:
(defmacro define-function (name expression)
;; Install EXPRESSION as the function value of NAME, returning NAME
;; This is just to avoid having to say `(setf ...)`: it should
;; probably do something at compile-time too so the compiler knows
;; the function will be defined.
`(progn
(setf (fdefinition ',name) ,expression)
',name))
(define-function fib
(let ((so-far '((2 . 1) (1 . 1) (0 . 0))))
(lambda (n)
(block fib
(check-type n (integer 0) "natural number")
(labels ((fibber (m)
(when (> m (car (first so-far)))
(push (cons m (+ (fibber (- m 1))
(fibber (- m 2))))
so-far))
(cdr (assoc m so-far))))
(fibber n))))))
This version of fib will share its cache between calls, which means it is a little faster, allocates a little less memory but may be less thread-safe:
> (time (fib 1000))
[...]
Allocation = 96072 bytes
[...]
> (time (fib 1000))
[...]
Allocation = 0 bytes
[...]
Interestingly memoization was invented (or at least named) by Donald Michie, who worked on breaking Tunny (and hence with Colossus), and who I also knew slightly: the history of computing is still pretty short!
Note that memoization is one of the times where you can end up fighting a battle with the compiler. In particular for a function like this:
(defun f (...)
...
;; no function bindings or notinline declarations of F here
...
(f ...)
...)
Then the compiler is allowed (but not required) to assume that the apparently recursive call to f is a recursive call into the function it is compiling, and thus to avoid a lot of the overhead of a full function call. In particular it is not required to retrieve the current function value of the symbol f: it can just call directly into the function itself.
What this means is that an attempt to write a function, memoize which can be used to mamoize an existing recursive function, as (setf (fdefinition 'f) (memoize #'f)) may not work: the function f still call directly into the unmemoized version of itself and won't notice that the function value of f has been changed.
This is in fact true even if the recursion is indirect in many cases: the compiler is allowed to assume that calls to a function g for which there is a definition in the same file are calls to the version defined in the file, and again avoid the overhead of a full call.
The way to deal with this is to add suitable notinline declarations: if a call is covered by a notinline declaration (which must be known to the compiler) then it must be made as a full call. From the spec:
A compiler is not free to ignore this declaration; calls to the specified functions must be implemented as out-of-line subroutine calls.
What this means is that, in order to memoize functions you have to add suitable notinline declarations for recursive calls, and this means that memoizing either needs to be done by a macro, or must rely on the user adding suitable declarations to the functions to be memoized.
This is only a problem because the CL compiler is allowed to be smart: almost always that's a good thing!
Your function unconditionally returns nums (but only if a variable called return exists). To see why, we can format it like this:
(defun fib (number)
(if (< number 2)
number
(push (+ (fib (- number 1)) (fib (- number 2))) nums))
return
nums)
If the number is less than 2, then it evaluates the expression number, uselessly, and throws away the result. Otherwise, it pushes the result of the (+ ....) expression onto the nums list. Then it uselessly evaluates return, throwing away the result. If a variable called return doesn't exist, that's an error situation. Otherwise, it evaluates nums and that is the return value.
In Common Lisp, there is a return operator for terminating and returning out of anonymous named blocks (blocks whose name is the symbol nil). If you define a named function with defun, then an invisible block exists which is not anonymous: it has the same name as that function. In that case, return-from can be used:
(defun function ()
(return-from function 42) ;; function terminates, returns 42
(print 'notreached)) ;; this never executes
Certain standard control flow and looping constructs establish a hidden anonymous block, so return can be used:
(dolist (x '(1 2 3))
(return 42)) ;; loop terminates, yields 42 as its result
If we use (return ...) but there is no enclosing anonymous block, that is an error.
The expression (return ...) is different from just return, which evaluates a variable named by the symbol return, retrieving its contents.
It is not clear how to repair your fib function, because the requirements are unknown. The side effect of pushing values into a global list normally doesn't belong inside a mathematical function like this, which should be pure (side-effect-free).
So you might know that if you know the two previous numbers you can compute the next. What comes after 3, 5? If you guess 8 you have understood it. Now if you start with 0, 1 and roll 1, 1, 1, 2, etc you collect the first variable until you have the number of numbers you'd like:
(defun fibs (elements)
"makes a list of elements fibonacci numbers starting with the first"
(loop :for a := 0 :then b
:for b := 1 :then c
:for c := (+ a b)
:for n :below elements
:collect a))
(fibs 10)
; ==> (0 1 1 2 3 5 8 13 21 34)
Every form in Common Lisp "returns" a value. You can say it evaluates to. eg.
(if (< a b)
5
10)
This evaluates either to 5 or 10. Thus you can do this and expect that it evaluates to either 15 or 20:
(+ 10
(if (< a b)
5
10))
You basically want your functions to have one expression that calculates the result. eg.
(defun fib (n)
(if (zerop n)
n
(+ (fib (1- n)) (fib (- n 2)))))
This evaluates to the result og the if expression... loop with :collect returns the list. You also have (return expression) and (return-from name expression) but they are usually unnecessary.
Your global variable num is actually not that a bad idea.
It is about to have a central memory about which fibonacci numbers were already calculated. And not to calculate those already calculated numbers again.
This is the very idea of memoization.
But first, I do it in bad manner with a global variable.
Bad version with global variable *fibonacci*
(defparameter *fibonacci* '(1 1))
(defun fib (number)
(let ((len (length *fibonacci*)))
(if (> len number)
(elt *fibonacci* (- len number 1)) ;; already in *fibonacci*
(labels ((add-fibs (n-times)
(push (+ (car *fibonacci*)
(cadr *fibonacci*))
*fibonacci*)
(cond ((zerop n-times) (car *fibonacci*))
(t (add-fibs (1- n-times))))))
(add-fibs (- number len))))))
;;> (fib 10)
;; 89
;;> *fibonacci*
;; (89 55 34 21 13 8 5 3 2 1 1)
Good functional version (memoization)
In memoization, you hide the global *fibonacci* variable
into the environment of a lexical function (the memoized version of a function).
(defun memoize (fn)
(let ((cache (make-hash-table :test #'equal)))
#'(lambda (&rest args)
(multiple-value-bind (val win) (gethash args cache)
(if win
val
(setf (gethash args cache)
(apply fn args)))))))
(defun fib (num)
(cond ((zerop num) 1)
((= 1 num) 1)
(t (+ (fib (- num 1))
(fib (- num 2))))))
The previously global variable *fibonacci* is here actually the local variable cache of the memoize function - encapsulated/hidden from the global environment,
accessible/look-up-able only through the function fibm.
Applying memoization on fib (bad version!)
(defparameter fibm (memoize #'fib))
Since common lisp is a Lisp 2 (separated namespace between function and variable names) but we have here to assign the memoized function to a variable,
we have to use (funcall <variable-name-bearing-function> <args for memoized function>).
(funcall fibm 10) ;; 89
Or we define an additional
(defun fibm (num)
(funcall fibm num))
and can do
(fibm 10)
However, this saves/memoizes only the out calls e.g. here only the
Fibonacci value for 10. Although for that, Fibonacci numbers
for 9, 8, ..., 1 are calculated, too.
To make them saved, look the next section!
Applying memoization on fib (better version by #Sylwester - thank you!)
(setf (symbol-function 'fib) (memoize #'fib))
Now the original fib function is the memoized function,
so all fib-calls will be memoized.
In addition, you don't need funcall to call the memoized version,
but just do
(fib 10)

How to avoid stackoverflow in clojure recursive function?

Here is an example:
;; Helper function for marking multiples of a number as 0
(def mark (fn [[x & xs] k m]
(if (= k m)
(cons 0 (mark xs 1 m))
(cons x (mark xs (inc k) m))
)))
;; Sieve of Eratosthenes
(defn sieve
[x & xs]
(if (= x 0)
(sieve xs)
(cons x (sieve (mark xs 1 x)))
))
(take 10 (lazy-seq (sieve (iterate inc 2))))
It produces a StackOverflowError.
There are a couple of issues here. First, as pointed out in the other answer, your mark and sieve functions don't have terminating conditions. It looks like they are designed to work with infinite sequences, but if you passed a finite-length sequence they'd keep going off the end.
The deeper problem here is that it looks like you're trying to have a function create a lazy infinite sequence by recursively calling itself. However, cons is not lazy in any way; it is a pure function call, so the recursive calls to mark and sieve are invoked immediately. Wrapping the outer-most call to sieve in lazy-seq only serves to defer the initial call; it does not make the entire sequence lazy. Instead, each call to cons must be wrapped in its own lazy sequence.
For instance:
(defn eager-iterate [f x]
(cons x (eager-iterate f (f x))))
(take 3 (eager-iterate inc 0)) ; => StackOverflowError
(take 3 (lazy-seq (eager-iterate inc 0))) ; => Still a StackOverflowError
Compare this with the actual source code of iterate:
(defn iterate
"Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects"
{:added "1.0"
:static true}
[f x] (cons x (lazy-seq (iterate f (f x)))))
Putting it together, here's an implementation of mark that works correctly for finite sequences and preserves laziness for infinite sequences. Fixing sieve is left as an exercise for the reader.
(defn mark [[x :as xs] k m]
(lazy-seq
(when (seq xs)
(if (= k m)
(cons 0 (mark (next xs) 1 m))
(cons x (mark (next xs) (inc k) m))))))
(mark (range 4 14) 1 3)
; => (4 5 0 7 8 0 10 11 0 13)
(take 10 (mark (iterate inc 4) 1 3))
; => (4 5 0 7 8 0 10 11 0 13)
Need terminating conditions
The problem here is both your mark and sieve functions have no terminating conditions. There must be some set of inputs for which each function does not call itself, but returns an answer. Additionally, every set of (valid) inputs to these functions should eventually resolve to a non-recursive return value.
But even if you get it right...
I'll add that even if you succeed in creating the correct terminating conditions, there is still the possibility of having a stack overflow if the depth of the recursion in too large. This can be mitigated to some extent by increasing the JVM stack size, but this has it's limits.
A way around this for some functions is to use tail call optimization. Some recursive functions are tail recursive, meaning that all recursive calls to the function being defined within it's definition are in the tail call position (are the final function called in the definition body). For example, in your sieve function's (= x 0) case, sieve is the tail call, since the result of sieve doesn't get passed into any other function. However, in the case that (not (= x 0)), the result of calling sieve gets passed to cons, so this is not a tail call. When a function is fully tail recursive, it is possible to behind the scenes transform the function definition into a looping construct which avoids consuming the stack. In clojure this is possible by using recur in the function definition instead of the function name (there is also a loop construct which can sometimes be helpful). Again, because not all recursive functions are tail recursive, this isn't a panacea. But when they are it's good to know that you can do this.
Thanks to #Alex's answer I managed to come up with a working lazy solution:
;; Helper function for marking mutiples of a number as 0
(defn mark [[x :as xs] k m]
(lazy-seq
(when-not (empty? xs)
(if (= k m)
(cons 0 (mark (rest xs) 1 m))
(cons x (mark (rest xs) (inc k) m))))))
;; Sieve of Eratosthenes
(defn sieve
[[x :as xs]]
(lazy-seq
(when-not (empty? xs)
(if (= x 0)
(sieve (rest xs))
(cons x (sieve (mark (rest xs) 1 x)))))))
I was adviced by someone else to use rest instead of next.

Infinite fibonacci series, take only n from the list, without using mutation?

I'm trying to solve this problem in a pure-functional way, without using set!.
I've written a function that calls a given lambda for each number in the fibonacci series, forever.
(define (each-fib fn)
(letrec
((next (lambda (a b)
(fn a)
(next b (+ a b)))))
(next 0 1)))
I think this is as succinct as it can be, but if I can shorten this, please enlighten me :)
With a definition like the above, is it possible to write another function that takes the first n numbers from the fibonacci series and gives me a list back, but without using variable mutation to track the state (which I understand is not really functional).
The function signature doesn't need to be the same as the following... any approach that will utilize each-fib without using set! is fine.
(take-n-fibs 7) ; (0 1 1 2 3 5 8)
I'm guessing there's some sort of continuations + currying trick I can use, but I keep coming back to wanting to use set!, which is what I'm trying to avoid (purely for learning purposes/shifting my thinking to purely functional).
Try this, implemented using lazy code by means of delayed evaluation:
(define (each-fib fn)
(letrec
((next (lambda (a b)
(fn a)
(delay (next b (+ a b))))))
(next 0 1)))
(define (take-n-fibs n fn)
(let loop ((i n)
(promise (each-fib fn)))
(when (positive? i)
(loop (sub1 i) (force promise)))))
As has been mentioned, each-fib can be further simplified by using a named let:
(define (each-fib fn)
(let next ((a 0) (b 1))
(fn a)
(delay (next b (+ a b)))))
Either way, it was necessary to modify each-fib a little for using the delay primitive, which creates a promise:
A promise encapsulates an expression to be evaluated on demand via force. After a promise has been forced, every later force of the promise produces the same result.
I can't think of a way to stop the original (unmodified) procedure from iterating indefinitely. But with the above change in place, take-n-fibs can keep forcing the lazy evaluation of as many values as needed, and no more.
Also, take-n-fibs now receives a function for printing or processing each value in turn, use it like this:
(take-n-fibs 10 (lambda (n) (printf "~a " n)))
> 0 1 1 2 3 5 8 13 21 34 55
You provide an iteration function over fibonacci elements. If you want, instead of iterating over each element, to accumulate a result, you should use a different primitive that would be a fold (or reduce) rather than an iter.
(It might be possible to use continuations to turn an iter into a fold, but that will probably be less readable and less efficient that a direct solution using either a fold or mutation.)
Note however that using an accumulator updated by mutation is also fine, as long as you understand what you are doing: you are using mutable state locally for convenience, but the function take-n-fibs is, seen from the outside, observationally pure, so you do not "contaminate" your program as a whole with side effects.
A quick prototype for fold-fib, adapted from your own code. I made an arbitrary choice as to "when stop folding": if the function returns null, we return the current accumulator instead of continuing folding.
(define (fold-fib init fn) (letrec ([next (lambda (acc a b)
(let ([acc2 (fn acc a)])
(if (null? acc2) acc
(next acc2 b (+ a b)))))])
(next init 0 1)))
(reverse (fold-fib '() (lambda (acc n) (if (> n 10) null (cons n acc)))))
It would be better to have a more robust convention to end folding.
I have written few variants. First you ask if
(define (each-fib fn)
(letrec
((next (lambda (a b)
(fn a)
(next b (+ a b)))))
(next 0 1)))
can be written any shorter. The pattern is used so often that special syntax called named let has been introduced. Your function looks like this using a named let:
(define (each-fib fn)
(let next ([a 0] [b 1])
(fn a)
(next b (+ a b))))
In order to get the control flowing from one function to another, one can in languages with supports TCO use continuation passing style. Each function gets an extra argument often called k (for continuation). The function k represents what-to-do-next.
Using this style, one can write your program as follows:
(define (generate-fibs k)
(let next ([a 0] [b 1] [k k])
(k a (lambda (k1)
(next b (+ a b) k1)))))
(define (count-down n k)
(let loop ([n n] [fibs '()] [next generate-fibs])
(if (zero? n)
(k fibs)
(next (λ (a next)
(loop (- n 1) (cons a fibs) next))))))
(count-down 5 values)
Now it is a bit annoying to write in style manually, so it could
be convenient to introduce the co-routines. Breaking your rule of not using set! I have chosen to use a shared variable fibs in which generate-fibs repeatedly conses new fibonacci numbers onto. The count-down routine merely read the values, when the count down is over.
(define (make-coroutine co-body)
(letrec ([state (lambda () (co-body resume))]
[resume (lambda (other)
(call/cc (lambda (here)
(set! state here)
(other))))])
(lambda ()
(state))))
(define fibs '())
(define generate-fib
(make-coroutine
(lambda (resume)
(let next ([a 0] [b 1])
(set! fibs (cons a fibs))
(resume count-down)
(next b (+ a b))))))
(define count-down
(make-coroutine
(lambda (resume)
(let loop ([n 10])
(if (zero? n)
fibs
(begin
(resume generate-fib)
(loop (- n 1))))))))
(count-down)
And a bonus you get a version with communicating threads:
#lang racket
(letrec ([result #f]
[count-down
(thread
(λ ()
(let loop ([n 10] [fibs '()])
(if (zero? n)
(set! result fibs)
(loop (- n 1) (cons (thread-receive) fibs))))))]
[produce-fibs
(thread
(λ ()
(let next ([a 0] [b 1])
(when (thread-running? count-down)
(thread-send count-down a)
(next b (+ a b))))))])
(thread-wait count-down)
result)
The thread version is Racket specific, the others ought to run anywhere.
Building a list would be hard. But displaying the results can still be done (in a very bad fashion)
#lang racket
(define (each-fib fn)
(letrec
((next (lambda (a b)
(fn a)
(next b (+ a b)))))
(next 0 1)))
(define (take-n-fibs n fn)
(let/cc k
(begin
(each-fib (lambda (x)
(if (= x (fib (+ n 1)))
(k (void))
(begin
(display (fn x))
(newline))))))))
(define fib
(lambda (n)
(letrec ((f
(lambda (i a b)
(if (<= n i)
a
(f (+ i 1) b (+ a b))))))
(f 1 0 1))))
Notice that i am using the regular fibonacci function as an escape (like i said, in a very bad fashion). I guess nobody will recommend programming like this.
Anyway
(take-n-fibs 7 (lambda (x) (* x x)))
0
1
1
4
9
25
64

Dolist evaluation error

I'm a CommonLisp noob with a question. I have these two functions below.
A helper function:
(defun make-rests (positions rhythm)
"now make those positions negative numbers for rests"
(let ((resultant-rhythm rhythm))
(dolist (i positions resultant-rhythm)
(setf (nth i resultant-rhythm) (* (nth i resultant-rhythm) -1)))))
And a main function:
(defun test-return-rhythms (rhythms)
(let ((positions '((0 1) (0)))
(result nil))
(dolist (x positions (reverse result))
(push (make-rests x rhythms) result))))
When I run (test-return-rhythms '(1/4 1/8)), it evaluates to: ((1/4 -1/8) (1/4 -1/8))
However, I expected: (test-return-rhythms '(1/4 1/8)) to evaluate to: ((-1/4 -1/8) (-1/4 1/8)).
What am I doing wrong?
Your implementation of make-rests is destructive.
CL-USER> (defparameter *rhythm* '(1/4 1/4 1/4 1/4))
*RHYTHM*
CL-USER> (make-rests '(0 2) *rhythm*)
(-1/4 1/4 -1/4 1/4)
CL-USER> *rhythm*
(-1/4 1/4 -1/4 1/4)
So, if you run your test, the second iteration will see (-1/4 -1/8), and (make-rests '(0) '(-1/4 -1/8)) returns (1/4 -1/8). Your use of let in make-rests does not copy the list, it just creates a new binding that references it. Use copy-list in your let, or write a non-destructive version in the first place:
(defun make-rests (positions rhythm)
(loop for note in rhythm
for i from 0
collect (if (member i positions) (* note -1) note)))

Resources