一个功能和一个宏
;; 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)
我不知道以下哪项是正确的:
cons-if-if成员是一种非破坏性的功能,pushnew-no-bells是一种破坏性的宏。
两者都是非破坏性的。
cons-if-if成员是一种非破坏性的功能,形容词“破坏性”和“非破坏性”不适用于宏。
以上都不是
我不知道pushnew是否被认为是破坏性的,但我想先通过放弃支持来简化事情。
答案 0 :(得分:3)
pushnew
改变了它的位置形式(第二个参数)的值,因此它是破坏性的:它改变了“就地”而不是仅创建一个新对象(可能与现有对象共享结构) 。您的cons-if-member
(最好称为cons-unless-member
或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)))
答案 1 :(得分:2)
cons-if-member
不具有破坏性。它也大致相当于ADJOIN。 pushnew-no-bells
修改一个地方,但不修改可能推送元素的列表结构。但是,它可以修改其他列表的结构,因为您可以将其用作(let ((list (list 1 2 3 4))) (pushnew-no-bells '1 (cddr list)))
。 (此外,形式list
将被评估两次,这不好(但也不是这个问题/答案的要点)。)这是破坏性的,因为它修改了那个地方,但它不是在某种意义上具有破坏性,例如nreverse
是nreverse
可以改变一个缺点列表的整个结构。
Hyperspec在破坏性和非破坏性之间没有完全相同的区别。例如,ADJOIN的规范就是说它做了什么
测试item是否与列表的现有元素相同。如果该项不是现有元素,则adjoin将其添加到列表中(如同cons)并返回结果列表;否则,不会添加任何内容并返回原始列表。
并且省略任何副作用。另一方面,PUSHNEW的文档在其语法部分提到它需要一个地方
pushnew 项目位置和键测试测试 - 不
=> 新就地值的
并且该解释提到它有副作用:。
新列表存储到位。 [强调补充] ......
副作用:
可以修改地点内容。
虽然破坏性和非破坏性捕获了关于如何实现事物的一些一般性想法,但实际的实现往往会更加微妙,因为程序员关心什么事物可能被破坏性地修改,什么样的状态可能会改变。
您正在使用的方法(即,在某个操作上进行功能实现,然后在其上实现修改宏)非常好,因为它将帮助您记录哪些函数和宏将具有副作用。它将帮助阅读该文档的任何人了解宏的预期副作用(只计算函数将计算的内容,然后将其存储回该位置)。如果你正在做很多事情(实际上,如果你正在做任何),你也应该好好看看DEFINE-MODIFY-MACRO这使得实现这些功能/宏对很容易,并且可以帮助你避免常见的陷阱(比如上面list
的双重评估)。