True destructive pop?
In #elisp, the pop
macro is not really destructive in the sense that you can use it to manipulate a memory location regardless of scope. It's only as destructive as setf
, i.e. it operates on "generalized places".
By contrast, destructive functions like delete
make use of the lower-level setcar
and setcdr
internally.
That tells us basically how to make a "true" pop
:
(defun true-pop (list) (setcar list (cadr list)) (setcdr list (cddr list)))
But something happens when the list has only one element left…
(setq foo '(a)) (true-pop foo) foo => (nil)
Compare with pop
:
(setq foo '(a)) (pop foo) foo => nil
There seems to be a fundamental limitation in Emacs Lisp: you can't take a memory address that points to a cons cell and make that cons cell be not a cons cell. Even delq
ceases to have a side-effect when the list has one element left: (delq 'a foo)
does not change foo
.
Why it works with pop
? Because you use it on a symbol that's in-scope, and it reassigns the symbol to a different memory address. Or that's how I understood it as of .
Workaround
There is a trick, if you still need "true pop" behavior. Let's say there's a list that you need to consume destructively, but the list is not stored in a symbol in the global obarray, but in a hash table value. To look up the hash table location every time would cost compute. So here's what we do: access the hash table once, let-bind the value, and manipulate only the cdr
cell of the value.
How the value might originally be stored:
(puthash KEY (cons t (make-list 100 "item")) TABLE)
Note that the car
is just t
, and we'll do nothing with it.
Now, consuming the list:
(let ((items (gethash KEY TABLE))) (while (cdr items) ... (DO-SOMETHING-WITH (cadr items)) ... (setcdr items (cddr items))))