В своей книге Об Лиспе Пол Грэм подчеркивает, что Лисп - это «расширяемый язык». Он говорит, что это означает создание все более высоких языковых интерфейсов в направлении языка, на котором приложение может быть полезно обсуждено или проанализировано. Это приводит к ортогональному языку ... на котором вы можете выразить много, комбинируя небольшое количество операторов множеством разных способов. В качестве эксперимента я хотел бы попытаться расширить одну из более полезных функций последовательности, а именно remove
.
Если отложить на время расширения, включающие непоследовательные типы данных (например, удаление элементов из массива, хэш-таблицы или списка свойств), еще есть место для расширения выбора ключевых слов. Например, отсутствует встроенное условие удаления элементов из последовательности на основе их индекса. Вдобавок к этому, программист может захотеть удалить элементы, значение которых совпадает с их индексом, с помощью теста, например, (lambda (elt idx) (= elt idx))
Подход без расширения будет состоять в том, чтобы просто свернуть вашу собственную итеративную функцию (еще одну из ста других трудно запоминаемых утилит), но она кажется более краткой, многократно используемой и эффективной, чтобы воспользоваться преимуществами встроенных функций и расширить их.
Непосредственная проблема заключается в том, что remove
применяется только в том случае, если имеется заданный элемент поиска, а для remove-if
требуется предикат, который принимает в качестве аргумента только один элемент (не элемент вместе с его индексом). Подход, который я хотел бы изучить, пытается объединить различные опции в одну функцию remove-sequence
, где последовательность является единственным обязательным аргументом, а все остальное является ключевым словом, адаптированным к конкретному виду необходимого удаления. Таким образом, элемент поиска указывается в ключевом слове: item, а логический: test с одним или двумя аргументами может включать как элемент, так и индекс, если необходимо. В этом последнем случае простой вызов может выглядеть как (remove-sequence '(3 1 2 4) :test (lambda (elt idx) (= x i)))
, удаляя третий элемент.
Я начал с функции, которая, кажется, работает на следующих примерах в SBCL:
(require :sb-introspect)
(defun remove-sequence (sequence &key item (test #'eql) from-end (start 0)
(end (length sequence)) (count (length sequence)) (key #'identity))
(cond (item
(remove item sequence :test test :from-end from-end
:start start :end end :count count :key key))
((= (length (sb-introspect:function-lambda-list test)) 1)
(remove-if test sequence :from-end from-end
:start start :end end :count count :key key))
((= (length (sb-introspect:function-lambda-list test)) 2)
(let* ((delta (if from-end -1 +1))
(initial-index (if from-end (length sequence) -1))
(closure (let ((index initial-index))
(lambda (element)
(setf index (+ index delta))
;(print (list element index))
(funcall test element index)))))
(remove-if closure sequence :from-end from-end
:start start :end end :count count :key key)))
(t (error "in remove-sequence macro"))))
(remove-sequence '(1 2 4 1 3 4 5) :test #'> :item 3) => (4 3 4 5)
(remove-sequence '(1 2 3 4 5 6) :test #'evenp :count 2 :from-end t) => (1 2 3 5)
(remove-sequence '(3 1 2 4) :test #'(lambda (elt idx) (= elt idx))) => (3 1 4)
Однако у меня возникли проблемы с преобразованием его в макрос, который пока выглядит следующим образом. (Он генерирует ошибку во время раскрытия макроса.)
(defmacro remove-sequence (sequence &key item test from-end start end count key)
(let ((tst (when test `(:test ,test)))
(frm-nd (when from-end `(:from-end ,from-end)))
(strt (when start `(:start ,start)))
(nd (when end `(:end ,end)))
(cnt (when count `(:count ,count)))
(ky (when key `(:key ,key)))
(test-fn (if test test #'eql)))
(cond (`,item
`(remove ,item ,sequence ,@tst ,@frm-nd ,@strt ,@nd ,@cnt ,@ky))
((= (length (sb-introspect:function-lambda-list test-fn)) 1)
`(remove-if ,test-fn ,sequence ,@frm-nd ,@strt ,@nd ,@cnt ,@ky))
((= (length (sb-introspect:function-lambda-list test-fn)) 2)
(let* ((delta (if `,from-end -1 +1))
(initial-index (if `,from-end (length `,sequence) -1))
(closure (let ((index initial-index))
(lambda (element)
(setf index (+ index delta))
;(print (list element index))
(funcall test-fn element index)))))
`(remove-if ,closure ,sequence ,@frm-nd ,@strt ,@nd ,@cnt ,@ky)))
(t (error "in remove-sequence macro")))))
Можно ли это исправить? Или есть лучший способ написать это? И, в целом, есть ли обратная сторона в прикреплении, может быть, дюжины или около того ключевых слов? Например, я бы по крайней мере хотел бы добавить логические ключевые слова для: duplicates и: destructive, а другие ключевые слова, вероятно, были бы актуальны для непоследовательных аргументов. Спасибо за любые опытные идеи.