将 Common Lisp "pushnew" 改编为 return success/failure

Adapting Common Lisp "pushnew" to return success/failure

Common Lisp 宏 pushnew returns 作为参数给出的(可能已更新的)位置列表。看来,如果您想知道给定项目是否实际被推送,则需要比较前后位置列表以查看它是否已更改。但这对我来说效率太低了,因为它会涉及重复比较两个复杂结构对象列表的等价性。

另一种可能有效的替代方法是使用 pushnew 的 :test 参数来记录是否已找到项目,因为列表元素已被扫描。如果找到该项目,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 参数是如何工作的,pushnew 的 Hyperspec 条目没有明确说明。是否保证列表元素的测试在测试 returns T 时立即停止,如所希望的那样? (或者 remove,测试应该继续到序列的末尾。)如果测试确实继续到最后,return-from 是否是提前终止的最佳方式?

另一个问题是匿名 lambda 主体重复检查每个元素的给定 ,test 参数。这可以移到 lambda 之外吗?感谢您的任何建议。

我认为更好的方法是检查新元素是否确实 prepended pushnew:

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

请注意,即使您将 :test 传递给 pushnew,您仍然会在 unlesswhen 中使用 eq

PS。请注意@coredump 评论中提到的极端情况。