Yes, Lisp has a macro for popping the N-th element of a list: it is called pop
.
$ clisp -q
[1]> (defvar list (list 0 1 2 3 4 5))
LIST
[2]> (pop (cdddr list))
3
[3]> list
(0 1 2 4 5)
[4]>
pop
works with any form that denotes a place.
The problem is that, unlike cddr
, nthcdr
isn't an accessor; a form like (nthcdr 3 list)
does not denote a place; it works only as a function call.
Writing a specialized form of pop
is not the best answer; rather, we can achieve a more general fix by writing a clone of nthcdr
which behaves like a place accessor. Then the pop
macro will work, and so will every other macro that works with places like setf
and rotatef
.
;; our clone of nthcdr called cdnth
(defun cdnth (idx list)
(nthcdr idx list))
;; support for (cdnth <idx> <list>) as an assignable place
(define-setf-expander cdnth (idx list &environment env)
(multiple-value-bind (dummies vals newval setter getter)
(get-setf-expansion list env)
(let ((store (gensym))
(idx-temp (gensym)))
(values dummies
vals
`(,store)
`(let ((,idx-temp ,idx))
(progn
(if (zerop ,idx-temp)
(progn (setf ,getter ,store))
(progn (rplacd (nthcdr (1- ,idx-temp) ,getter) ,store)))
,store))
`(nthcdr ,idx ,getter)))))
Test:
$ clisp -q -i cdnth.lisp
;; Loading file cdnth.lisp ...
;; Loaded file cdnth.lisp
[1]> (defvar list (list 0 1 2 3 4 5))
LIST
[2]> (pop (cdnth 2 list))
2
[3]> list
(0 1 3 4 5)
[4]> (pop (cdnth 0 list))
0
[5]> list
(1 3 4 5)
[6]> (pop (cdnth 3 list))
5
[7]> list
(1 3 4)
[8]> (pop (cdnth 1 list))
3
[9]> list
(1 4)
[10]> (pop (cdnth 1 list))
4
[11]> list
(1)
[12]> (pop (cdnth 0 list))
1
[13]> list
NIL
[14]>
A possible improvement to the implementation is to analyze the idx
form and optimize away the generated code that implements the run-time check on the value of idx
. That is to say, if idx
is a constant expression, there is no need to emit the code which tests whether idx
is zero. The appropriate code variant can just be emitted. Not only that, but for small values of idx
, the code can emit special variants based on the "cadavers": cddr
, cdddr
, rather than the general nthcdr
. However, some of these optimizations might be done by the Lisp compiler and thus redundant.
remove-at
. Also, there is no need for macro here. – Shaner