pushnew without place support. Is it considered a

2019-09-04 04:03发布

问题:

One function and one macro

;; 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)

I do not know which of the following is correct:

  1. cons-if-member is a non-destructive function and pushnew-no-bells is a destructive macro.

  2. Both are non-destructive.

  3. cons-if-member is a non-destructive function and the adjectives "destructive" and "non-destructive" do not apply to macros.

  4. none of the above

I do not have any idea on whether pushnew is considered destructive or not either, but I wanted to make things simple by dropping place support first.

回答1:

cons-if-member is not destructive. It is also roughly equivalent to ADJOIN. pushnew-no-bells modifies a place, but does not modify the structure of the list onto which an element might be pushed. However, it can modify the structure of other lists, because you could use it as (let ((list (list 1 2 3 4))) (pushnew-no-bells '1 (cddr list))). (Also, the form list will be evaluated twice, which is not good (but also not the main point of this question/answer).) This is destructive in the sense that it modifies that place, but it is not destructive in the sense that, e.g., nreverse is (nreverse can change the entire structure of a cons list).

The Hyperspec doesn't make quite the same distinction between destructive and non-destructive. The spec for ADJOIN, for instance, just says what it does

Tests whether item is the same as an existing element of list. If the item is not an existing element, adjoin adds it to list (as if by cons) and returns the resulting list; otherwise, nothing is added and the original list is returned.

and omits any mention of side effects. The documentation for PUSHNEW, on the other hand, mentions in its syntax section that it requires a place

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

and the decription mentions that it has side effects:.

the new list is stored in place. [emphasis added] …

Side Effects:
The contents of place may be modified.

While destructive and non-destructive capture some general ideas about how things are implemented, actual implementations tend to be a little bit more subtle, because the programmer is concerned with what things might be destructively modified, and what sort of state might be changed.

The approach that you are using (that is, making a functional implementation of some operation and then implementing a modifying macro on top of it), however, is very good, as it will help you document what functions and macros will have side effects. It will help anyone reading that documentation understand what the intended side effects of the macro is (just compute what the function would compute, and then store it back to the place). If you're doing much of this (actually, if you're doing any of this), you should also probably take a good look at DEFINE-MODIFY-MACRO which makes implementing these kinds of function/macro pairs very easy, and will help you avoid common pitfalls (like the double evaluation of list above).



回答2:

pushnew changes the value of it's place-form (2nd argument), so it is destructive: it changes something "in place" instead of just creating a new object (which may share structure with an existing one). Your cons-if-member (which would better be called cons-unless-member or cons-if-not-member) does not modify anything "in place", so it's actually non-destructive.

Note, BTW., that you cannot really exclude "general place" support, due to the presence of symbol macros. Observe:

(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)))


标签: lisp