I'm trying to write a function that will destructively remove N elements from a list and return them. The code I came up with (see below) looks fine, except the SETF is not working the way I intended.
(defun pick (n from)
"Deletes (destructively) n random items from FROM list and returns them"
(loop with removed = nil
for i below (min n (length from)) do
(let ((to-delete (alexandria:random-elt from)))
(setf from (delete to-delete from :count 1 :test #'equal)
removed (nconc removed (list to-delete))))
finally (return removed)))
For most cases, this works just fine:
CL-USER> (defparameter foo (loop for i below 10 collect i))
CL-USER> (pick 3 foo)
(1 3 6)
CL-USER> foo
(0 2 4 5 7 8 9)
CL-USER> (pick 3 foo)
(8 7 0)
CL-USER> foo
(0 2 4 5 9)
As you can see, PICK works just fine (on SBCL) unless the element being picked happens to be the first on the list. In that case, it doesn't get deleted. That's because the only reassignment happening is the one that goes on inside DELETE. The SETF doesn't work properly (i.e. if I use REMOVE instead, FOO does not change at all).
Is there any scoping rule going on that I'm not aware of?