Search code examples
common-lispsbcl

What does the delete function do to an array?


I am trying to delete an element from an array using the delete function in Common Lisp (SBCL) but noticed that all the indices of this array are still present (the return value of (length arr) on the array is unchanged) after a call to delete:

(defparameter *x* (make-array 3 :initial-contents (list 13 26 39)))
*x* ; #(13 26 39)
(delete 26 *x* :test #'equal) ; #(13 39)

*x* ; #(13 39 39)
(length *x*) ; 3

I assume that the second element of *X* is still accessible after calling delete because arrays are contiguous chunks of memory where indices cannot be 'removed' without creating a new array.

My confusion comes from using setf in conjunction with delete. This only allows the user access to elements that were not affected by delete:

(defparameter *y* (make-array 3 :initial-contents (list 13 26 39)))
*y* ; #(13 26 39)
(setf *y* (delete 26 *y* :test #'equal)) ; #(13 39)

*y* ; #(13 39)
(length *y*) ; 2

Does this mean that delete returns a new array when called on arrays? Or does the setf call create a new array so that (setf (delete ...)) effectively does the same thing as (setf (remove ...))? Or is the *Y* from above pointing at the same array the whole time, just the second element was somehow 'ignored' behind the scenes?


Solution

  • If you read the Common Lisp standard (usually the Common Lisp HyperSpec), then it says:

    delete item sequence &key from-end test test-not start end count key => result-sequence

    ...

    delete, delete-if, and delete-if-not are like remove, remove-if, and remove-if-not respectively, but they may modify sequence.

    sequence is the supertype of vector and list. Thus the function delete works for vectors and lists.

    delete item sequence denotes the required arguments.

    but they may modify sequence then means that the function may modify the original sequence provided as an argument. may modify means that an implementations can differ in behavior.

    But in any case you need to use the returned result sequence. That's also relatively easy to remember, since many (but not all) functions typically return results and it is preferred to think in terms of computed results and not as a procedure with side effects.

    Note that vectors in Common Lisp are mutable objects. One can change their contents, without allocating a new object. Also note: Common Lisp provides destructive functions which may modify the argument objects. For some functions the behavior is intended and for some functions they are side effects not to be used. delete is such a latter function: the argument sequence can be changed and that object should no longer be used.

    Example 1:

    (defparameter *x* (make-array 3 :initial-contents (list 13 26 39)))
    

    Above creates a vector object of length 3 with the provided contents. The variable *x* points to this object.

    *x* ; #(13 26 39)
    

    Above: The variable *x* still points to the originally created object.

    (delete 26 *x* :test #'equal) ; #(13 39)
    

    Above: #(13 39) is returned from the delete call. This value is not used, just returned. The variable *x* still points to the originally created vector object. That object might have been changed.

    *x* ; #(13 39 39)
    

    Above: The variable *x* still points to the originally created object. The object has been destructively changed by the delete call.

    (length *x*) ; 3
    

    Above: The variable *x* still points to the originally created object. It's length is still 3.

    Example 2:

    (defparameter *y* (make-array 3 :initial-contents (list 13 26 39)))
    

    Above creates a vector object of length 3 with the provided contents. The variable *y* points to this object.

    *y* ; #(13 26 39)
    

    Above: The variable *y* still points to the originally created object.

    (setf *y* (delete 26 *y* :test #'equal)) ; #(13 39)
    

    Above: the variable *y* is set to the returned object from the delete call: #(13 39).

    *y* ; #(13 39)
    

    Above: The variable *y* points to the new, from delete returned, object.

    (length *y*) ; 2
    

    Above: The variable *y* still points to the new, from delete returned, object. The length of that object is 2.