pushnew没有地方支持。它被认为是破坏性的宏观吗?

时间:2013-05-28 10:21:12

标签: lisp

一个功能和一个宏

;; 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-if成员是一种非破坏性的功能,pushnew-no-bells是一种破坏性的宏。

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

  3. cons-if-if成员是一种非破坏性的功能,形容词“破坏性”和“非破坏性”不适用于宏。

  4. 以上都不是

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

2 个答案:

答案 0 :(得分:3)

pushnew改变了它的位置形式(第二个参数)的值,因此它是破坏性的:它改变了“就地”而不是仅创建一个新对象(可能与现有对象共享结构) 。您的cons-if-member(最好称为cons-unless-membercons-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不具有破坏性。它也大致相当于ADJOINpushnew-no-bells修改一个地方,但不修改可能推送元素的列表结构。但是,它可以修改其他列表的结构,因为您可以将其用作(let ((list (list 1 2 3 4))) (pushnew-no-bells '1 (cddr list)))。 (此外,形式list将被评估两次,这不好(但也不是这个问题/答案的要点)。)这是破坏性的,因为它修改了那个地方,但它不是在某种意义上具有破坏性,例如nreversenreverse可以改变一个缺点列表的整个结构。

Hyperspec在破坏性和非破坏性之间没有完全相同的区别。例如,ADJOIN的规范就是说它做了什么

  

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

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

  

pushnew 项目位置和键测试测试 - 不
  => 新就地值

并且该解释提到它有副作用:。

  

新列表存储到位。 [强调补充] ......

     

副作用:
可以修改地点内容。

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

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