drlat <
l...@dayspringpublisher.com> writes:
> I looped through a list of records (plists actually), displaying them
> one at a time. When one needed to be deleted, I intended to use
>
> (push record *records-to-remove*)
>
> to populate *records-to-remove*. However, no records needed to be
> removed. Nevertheless, once the loop was finished the following code
> automatically ran:
>
> (setf *db* (dolist (record-to-remove *records-to-remove*)
> (remove record-to-remove *db* :test #'equal)))
>
> to delete any unwanted records. Since *records-to-remove* was empty, no
> records should have been removed. To my surprise this deleted every
> record in *db*. Studying CLHS, it seems that for several reasons 'remove
> is not going to do what is needed. In fact, can #'equal even be used to
> test for a unique plist in a list of plists? I don't understand why any
> records were deleted at all.
So you read CLHS remove.
remove, remove-if, and remove-if-not return a sequence from which
the elements that satisfy the test have been removed.
delete, delete-if, and delete-if-not are like remove, remove-if, and
remove-if-not respectively, but they may modify sequence.
First notice that DELETE is like REMOVE, so that using it won't help
you, if it doesn't work with REMOVE.
Reading the rest of the description, you should learn that remove will
return a fresh list if the element is present in the original list, and
that fresh list may share structure with the original list up to being
the same if the element wasn't present in the original list in the first
place.
Ok, the keyword is "RETURN".
In:
> (setf *db* (dolist (record-to-remove *records-to-remove*)
> (remove record-to-remove *db* :test #'equal)))
there are three results returned:
- the one returned by SETF, wich is the value assigned to *DB*, which is
the same result as returned by DOLIST, and which is printed by the
REPL.
- the one returned by DOLIST. Have you read clhs dolist? This is the
clhs page you should have read… What does DOLIST return?
- the one returned by REMOVE, which you blissfully ignore. So
your call to remove is almost dead code. A compiler could generate
this instead:
(dolist (record-to-remove *records-to-remove*)
(unless (or (vectorp record-to-remove)
(proper-list-p record-to-remove))
(error 'type-error :datum record-to-remove
:expected-type '(or vector proper-list))))
> I had a backup, so no big loss. But I haven't been able to figure out
> how to do what needs to be done to make this work properly. Any help
> will be appreciated.
This is where the notion of functional style comes to play. A lot of
operators in lisp are functional operators: they mostly have no side
effects. The only side effects of REMOVE are the side effects in the
mutator (allocation of memory) and that if the argument is not of the
right type, it will signal an error. If you give it the right type of
argument, then it won't have side effects. The only useful thing of
functions that don't have side effects, are their results. If you don't
use the results, then you're not doing anything.
So, what's the result of REMOVE again? Yes, a list with the element
removed. Perhaps that's what you're after, aren't you?
So the first solution would be:
(dolist (record *records-to-remove*)
(setf *db* (remove record *db*)))
You don't need to use any other test than EQL here. On the contrary, it
may be bad to use another test. Suppose you want to remove a duplicate
record:
(setf *db* (list '(:one 1 :two 2) '(:one 1 :two 2)))
(remove (first *db*) *db*)
--> ((:one 1 :two 2))
(remove (first *db*) *db* :test 'equal)
--> nil
Now, after having read clhs dolist, you may know that the result of
dolist is specified as a third element in the first argument to dolist.
So you could write something like:
(setf *db* (let ((db *db*))
(dolist (item *records-to-remove* db)
(setf db (remove item db)))))
which is better, because the side effects of (setf db …) are limited to
the LET DB form. We're assigning to *db* only once, with the new list.
Finally, notice that if the same identical (eq) record cannot be present
several times in the *DB*, and if the order doesn't matter, then you can
consider the list as a set, and use set-difference:
(setf *db* (set-difference *db* *records-to-remove*))
--
__Pascal Bourguignon__
http://www.informatimago.com/
A bad day in () is better than a good day in {}.