0

一功能一宏

;; I am sorry for the confusing function name.
;; As one answer suggests, cons-if-not-member is the better name.
(defun cons-if-member (element list)
  (if (member element list)
      list
    (cons element list)))

(defmacro pushnew-no-bells (element list)
  "pushnew without place support"
  `(setq ,list (cons-if-member ,element ,list)))

(let ((xx (list 1 2)))
  (pushnew-no-bells 0 xx)
  xx)

我不知道以下哪个是正确的:

  1. cons-if-member 是一个非破坏性函数,而 pushnew-no-bells 是一个破坏性宏。

  2. 两者都是非破坏性的。

  3. cons-if-member 是一个非破坏性函数,形容词“破坏性”和“非破坏性”不适用于宏。

  4. 以上都不是

我也不知道 pushnew 是否被认为是破坏性的,但我想通过首先放弃地方支持来简化事情。

4

2 回答 2

3

pushnew改变了它的位置形式的值(第二个参数),所以它是破坏性的:它改变了“就地”的东西,而不是仅仅创建一个新对象(它可能与现有对象共享结构)。您的cons-if-member(最好称为cons-unless-memberor cons-if-not-member)不会“就地”修改任何内容,因此它实际上是非破坏性的。

请注意,顺便说一句,由于符号宏的存在,您不能真正排除“一般位置”支持。观察:

(defclass foo ()
  ((x :initform nil)))

(let ((instance (make-instance 'foo)))
  (with-slots (x) instance
    (pushnew-no-bells 1 x))
  (format t "~&Now: ~S~%" (slot-value instance 'x)))
于 2013-05-28T13:15:03.797 回答
2

cons-if-member没有破坏性。它也大致相当于ADJOINpushnew-no-bells修改一个地方,但不修改一个元素可能被推送到的列表的结构。但是,它可以修改其他列表的结构,因为您可以将其用作(let ((list (list 1 2 3 4))) (pushnew-no-bells '1 (cddr list))). (此外,表格list将被评估两次,这是不好的(但也不是这个问题/答案的重点)。)从修改那个地方的意义上说,这是破坏性的,但从某种意义上说,它不是破坏性的,例如nreverseis (nreverse可以改变 cons 列表的整个结构)。

Hyperspec 在破坏性和非破坏性之间没有完全相同的区别。例如,ADJOIN 的规范只是说明了它的作用

测试 item 是否与列表的现有元素相同。如果该项目不是现有元素,则 adjoin 将其添加到列表中(就像通过 cons 一样)并返回结果列表;否则,不添加任何内容并返回原始列表。

并省略了任何副作用的提及。另一方面,PUSHNEW的文档在其语法部分中提到它需要一个位置

pushnew item place &key key test test-not
=> new-place-value

并且描述中提到它有副作用:。

新列表存储在适当的位置。[强调] ...</p>

副作用:
地方的内容可能会被修改。

虽然破坏性和非破坏性捕获了一些关于事物如何实现的一般概念,但实际实现往往更微妙一些,因为程序员关心的是哪些事物可能会被破坏性地修改,以及可能会改变什么样的状态。

但是,您正在使用的方法(即,对某些操作进行函数式实现,然后在其上实现修改宏)非常好,因为它将帮助您记录哪些函数和宏会产生副作用。它将帮助阅读该文档的任何人了解宏的预期副作用是什么(只需计算函数将计算的内容,然后将其存储回该位置)。如果您正在做很多事情(实际上,如果您正在做任何事情),您还应该好好看看DEFINE-MODIFY-MACRO,它使得实现这些类型的函数/宏对非常容易,并且会帮助您避免常见的陷阱(如上面的双重评估list)。

于 2013-05-28T13:22:22.560 回答