Адаптация Common Lisp «pushnew» для возврата успеха / неудачи - PullRequest
1 голос
/ 21 мая 2019

Макрос Common Lisp pushnew возвращает (возможно обновленный) список мест, заданный в качестве аргумента.Похоже, что если вы хотите узнать, был ли данный элемент фактически сдвинут или нет, вам нужно сравнить список мест до и после, чтобы увидеть, изменился ли он.Но это слишком неэффективно для моего использования, так как это потребовало бы многократного сравнения двух списков объектов со сложной структурой для эквивалентности.

Другая альтернатива, которая может сработать, - это использовать аргумент: test для pushnew, чтобы записать, еслиэлемент еще не найден, так как элементы списка сканируются.Если элемент найден, T может быть возвращено как второе кратное значение, иначе NIL.Следующий макрос пытается сделать это:

(defmacro pushnew+p (item place &key test (key #'identity) &aux old?)
  "Same as pushnew, but also returns whether item was pushed."
  `(values (pushnew ,item ,place
                    :test (lambda (item element)
                            (if ,test
                              (setf old? (funcall ,test item element))
                              (setf old? (eql item element))))
                    :key ,key)
           (not old?)))

Кажется, он работает нормально, так как

(defparameter x '(1 2 3))

(pushnew+p 0 x) -> (0 1 2 3), T

(pushnew+p 2 x) -> (0 1 2 3), NIL

(defparameter y '((1) (2) (3)))

(pushnew+p '(0) y :test #'equal) -> ((0) (1) (2) (3)), T

(pushnew+p '(2) y :test #'equal) -> ((0) (1) (2) (3)), NIL

Однако я не уверен в том, как работает аргумент: test и Hyperspecзапись для pushnew прямо не говорит.Гарантируется ли, что тестирование элементов списка прекращается, как только тест возвращает T, как требуется?(В качестве альтернативы для remove тестирование должно продолжаться до конца последовательности.) Если тестирование продолжается до конца, будет ли return-from лучшим способом завершить работу раньше?

Другая проблема заключается вчто анонимное лямбда-тело повторно проверяет заданный аргумент ,test для каждого элемента.Можно ли это перенести за пределы лямбды?Спасибо за любой совет.

1 Ответ

6 голосов
/ 21 мая 2019

Я думаю, что лучший способ - проверить, является ли новый элемент фактически с добавлением , как указано pushnew:

(unless (eq item (car place))
  (pushnew item place ...)
  (when (eq item (car place))
    ;; not found, prepended
    ))

Примечаниечто даже если вы передадите :test на pushnew, вы все равно будете использовать eq в unless и when.

PS.Обратите внимание на угловой случай, упомянутый в комментарии @ coredump.

...