Structure mutation

Course links

In the reading on vectors, we developed some procedures that took vectors as arguments and constructed and returned other vectors as results -- for instance, the double-every-element procedure:

;;; double-every-element: construct a vector similar to a given vector of
;;; numbers, but with each element doubled

;; Given:
;;   VEC, a vector of numbers.

;; Result:
;;   DOUBLE-VEC, a vector of numbers.

;; Preconditions:
;;   None.

;; Postconditions:
;;   (1) The length of DOUBLE-VEC is equal to the length of VEC.
;;   (2) Every element of DOUBLE-VEC is twice the corresponding element of
;;       VEC.

(define double-every-element
  (lambda (vec)
    ((vector-generator (lambda (position)
                         (* 2 (vector-ref vec position))))
     (vector-length vec))))

As we saw in the reading on side effects, however, it is sometimes appropriate to replace the elements of the original vector with the newly computed elements, instead of constructing a completely new vector. For instance, we could write a double-every-element! procedure that doubles each element of a vector in place. The following interactions demonstrate the effect of a call to this procedure:

> (define sample-vector (vector 3 1 4 1 5 9))
> sample-vector
#(3 1 4 1 5 9)
> (double-every-element! sample-vector)
> sample-vector
#(6 2 8 2 10 18)

Here's how to write it:

;;; double-every-element!: destructively replace every element of a vector
;;; of numbers with its double.

;; Given:
;;   VEC, a vector of numbers.

;; Results:
;;   None.

;; Preconditions:
;;   None.

;; Postcondition:
;;   Every element of VEC is twice the element initially stored at the
;;   same position in VEC.

(define double-every-element!
  (lambda (vec)
    (let ((size (vector-length vec)))
      (let kernel ((position 0))
        (if (< position size)
            (begin
              (vector-set! vec position (* 2 (vector-ref vec position)))
              (kernel (+ position 1))))))))

As the exclamation point at the end of double-every-element! indicates, calling the procedure causes an irreversible change of state in its argument, sample-vector. The original contents of that vector are gone. Each of the original elements has been replaced by its double. This ``destructive'' procedure is likely to be more efficient than double-every-element, because it is unnecessary to allocate storage for a result vector, but it is also somewhat trickier to use. The timing is important: one must not replace the elements of the vector too soon, at a time when one still needs some of the old values for other computations.

This pattern of computation is common enough that it is useful to define a destructive version of vector-map. Here is a demonstration of the effect of this vector-map! procedure:

> (define sample-vector (vector 3 1 4 1 5 9))
> (vector-map! square sample-vector)
> sample-vector
#(9 1 16 1 25 81)

And here is its definition:

;;; vector-map!: replace each element of a given vector with the result
;;; of applying a given procedure to that element

;; Given:
;;   PROC, a unary procedure.
;;   VEC, a vector.

;; Results:
;;   None.

;; Precondition:
;;   Every element of VEC satisfies any conditions that PROC imposes on its
;;   argument.

;; Postcondition:
;;   Each element of VEC is the result of applying PROC to the value
;;   initially stored at the same position in VEC.

(define vector-map!
  (lambda (proc vec)
    (let ((size (vector-length vec)))
      (let kernel ((position 0))
        (if (< position size)
            (begin
              (vector-set! vec position (proc (vector-ref vec position)))
              (kernel (+ position 1))))))))

Mutators for other data structures

Other built-in Scheme data structures have mutator procedures as well:

The set-cdr! procedure in particular should be used with great caution, since it is all too easy to create (by accident) a data structure for which the box-and-pointer diagram contains a cycle -- for instance, a structure in which box A contains a pointer to box B, which contains a pointer back to box A.

(a box-and-pointer diagram with two boxes,
each containing a pointer to the other)

When a list-recursive procedure is confronted with such a structure, a runaway recursion results, since you can't reach a null list by repeatedly taking the cdr. Even printing out such a structure can be problematical, as the following interaction with the SCM implementation of Scheme demonstrates:

> (define a-box (cons 'a 'who-cares))
#<unspecified>
> (define b-box (cons 'b 'irrelevant))
#<unspecified>
> (set-cdr! a-box b-box)
#<unspecified>
> (set-cdr! b-box a-box)
#<unspecified>
> a-box
(a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
 a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
user interrupt 

Since the procedure that does the printing works by writing out the car and then recursively dealing with the cdr, the runaway recursion produces runaway output, continuing until the user steps in to interrupt the computation. DrScheme avoids this problem by using a special output syntax for pair structures containing cycles:

> a-box
#0=(a b . #0#)