Да, в Лиспе есть макрос для выталкивания N-го элемента списка: он называется 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
работает с любой формой, обозначающей место.
Проблема в том, что, в отличие от cddr
, nthcdr
не является средством доступа;форма, подобная (nthcdr 3 list)
, не обозначает место;он работает только как вызов функции.
Написание специализированной формы pop
не лучший ответ;скорее, мы можем добиться более общего исправления, написав клон nthcdr
, который ведет себя как средство доступа к месту.Тогда будет работать макрос pop
, а - и любой другой макрос , который работает с такими местами, как setf
и 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)))))
Тест:
$ 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]>
Возможным улучшением реализации является анализ формы idx
и оптимизация сгенерированного кода, который реализует проверку во время выполнения значения idx
.То есть, если idx
является константным выражением, нет необходимости генерировать код, который проверяет, равен ли idx
нулю.Соответствующий вариант кода может быть просто выдан.Мало того, но для небольших значений idx
код может выдавать специальные варианты, основанные на «трупах»: cddr
, cdddr
, а не общие nthcdr
.Однако некоторые из этих оптимизаций могут быть выполнены компилятором Lisp и, таким образом, являются избыточными.