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:
cons-if-member is a non-destructive function and pushnew-no-bells is a destructive macro.
Both are non-destructive.
cons-if-member is a non-destructive function and the adjectives "destructive" and "non-destructive" do not apply to macros.
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.
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). Yourcons-if-member
(which would better be calledcons-unless-member
orcons-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:
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 formlist
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
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
and the decription mentions that it has side effects:.
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).