<<

201, Prof. Dana Angluin 1

Mutating Cells

We describe the use of the Scheme mutators -car! and set-cdr!, including loops of pointers, and dotted pairs, and the predicates eq? and pair?

The mutators set-car! and set-cdr!

The mutators set-car! and set-cdr! change the values of the car and cdr portions of a cons cell, respec- tively. They are constant time, or O(1), operations. (NOTE: lists and other structures created using the special form quote are “immutable”, which means that they are not intended to be changed using set-car! and set-cdr! If you create a list using quote and change parts of it using set-car! and set-cdr!, the results are undefined, and vary from one Scheme interpreter to another. Thus, to be sure of the effects, use cons or list to create list structures you intend to modify using set-car! or set-cdr! In Racket, you will need to use mcons in place of cons to create mutable cons cells if you wish to change their fields with set-car! and set-cdr!) Imagine we start as follows:

(define x (cons 17 (cons 14 ’())))

so that the value of x, represented in a box and pointer diagram, is as follows.

------| | | | | | x--->| 17 | ------>| 14 | () | | | | | | | ------

If we now execute

(define y (cons 13 x))

then the cons creates a new cons cell, and sets its car to 13 and its cdr to the value of x, resulting in the structure:

------| | | y--->| 13 | ------| | | | ------| | v ------| | | | | | x------>| 17 | ------>| 14 | () | | | | | | | ------Computer Science 201, Prof. Dana Angluin 2

Note that the list that is the value of x is a substructure of the list that is the value of y. This really only becomes relevant if you can mutate the values in a list, which set-car! and set-cdr! allow you to do. For this example, the result of

(set-car! y (cdr x)) is to replace the car part of y with the cdr part of x, resulting in the structure:

------| | | y--->| | | ------| | | | | ---|------| | | ------| | | | v v ------| | | | | | x------>| 17 | ------>| 14 | () | | | | | | | ------

The value of x is unchanged, but we now have:

y => ((14) 17 14)

To see this, note that the car part of y is a pointer to the list (14), so this is the first element of the list that is the value of y. The cdr part of y points to the list (17 14), so these are the rest of the elements on the list. If we now execute the command:

(set-cdr! x y) we set the cdr of x to the value of y, which results in the structure:

------| | | y--->| | | ------>| | | | | | ---|------| | | | | ------| | | ------| | | | Computer Science 201, Prof. Dana Angluin 3

v | v ------|------| | | | | | | x------>| 17 | | | | 14 | () | | | | | | | ------

Note that this structure has loops of pointers in it; nothing prevents us from doing that. However, when we try to evaluate y, the result depends on the Scheme system we are using. A naive Scheme interpreter might follow the pointers around in circles and print out an endless “list” as follows:

> y ((14) 17 (14) 17 (14) 17 (14) 17 (14) 17 (14) ...

However, the Dr. Racket implementation of R5RS is rather cleverer, and prints out the following:

> y #0=((14) 17 . #0#)

There are two unfamiliar aspects of the value printed out: the #0 and the dot (.) The #0 is intended to indicate a pointer, so that the item after the dot is in fact a pointer back to the structure itself. The dot is part of a “dotted pair”. The second argument to cons need not be a list. In particular, we have:

> (cons 12 15) (12 . 15)

What cons does is allocate a new cons cell, evaluate its two arguments and put the value of the first argument in the car part of the cons cell, and the value of the second argument in the cdr part of the cons cell. In this case the result is a dotted pair with car part 12 and cdr part 15. In order to show the contents of the dotted pair, Scheme prints out the two values, enclosed in parentheses and separated by a dot. As another example:

> (cons 33 (cons 12 15)) (33 12 . 15)

Thus, the value printed out for y above indicates a cons cell with car part pointing to the list (14) and a cdr part pointing to a cons cell with car part 17 and cdr part pointing back to the initial cons cell.

Pointers and the predicate eq?

To deal with structures of pointers, we would like to be able to compare pointers for equality, which is what the predicate eq? does for us. It returns #t if its two arguments evaluate to the same pointer, #f if they do not. In terms of the list representation discussed above, this would be implemented by testing whether the two pointers had equal address fields. The predicate eq? is constant time. The predicate equal? is not in general constant time; it can be used to compare the contents of two lists of arbitrary length. Consider the following: Computer Science 201, Prof. Dana Angluin 4

> (define x (list ’a ’b)) > (define y (list ’a ’b)) > (equal? x y) #t > (eq? x y) #f >

The two quoted lists create two separate structures of two cons cells each, which have the same contents, but occupy different locations in memory.

Testing circularity

As an illustration of this of pointer-oriented thinking about cons cells, we write a procedure to test whether a list is a flat list or is circular. By circular we mean that the “last” cdr points back to the first cons cell in the structure, instead of being the null list. As examples of the two possible kinds of input, we consider the following. A flat list of 3 elements:

------| | | | | | | | | x--->| 2 | ------>| 4 | ------>| 3 | () | | | | | | | | | | ------

A circular “list” of three elements:

------| | | ------| --->| | | | | | | | | | x--->| 2 | ------>| 4 | ------>| 3 | ------| | | | | | | | | ------

Our procedure should return #t if the structure is of the second kind, and #f if the structure is of the first kind. (It may fail to terminate on other kinds of structures with cycles of pointers.)

(define circular? (lambda (ls) (if (null? ls) #f (circular-help? ls (cdr ls)))))

(define circular-help? (lambda (first next) (cond ((eq? first next) #t) Computer Science 201, Prof. Dana Angluin 5

((null? next) #f) (else (circular-help? first (cdr next))))))

If the input is a null list, circular? just returns #f. Otherwise, it calls an auxiliary procedure with two arguments, the initial pointer to the structure (x above), and the cdr part from the first cons cell in the structure, which is a pointer to the second cons cell from the left. What the auxiliary procedure does is compare its two input pointers for equality using eq?. If they are equal, the list is circular in the sense described above, and the procedure returns the value #t. If they are different, the procedure checks whether next is the null list, signifying that the list is not circular, in which case, it returns the value #f. If neither of these conditions is true, it calls itself recursively with the cdr part of the cons cell pointed to by next. Thus, it will continue down the structure, comparing cdr parts to the initial pointer to the structure until it either gets the null list or the eq? test succeeds. Note that if we had a slightly different structure in which the cdr part of the “last” cons cell pointed back to the second cons cell from the left, this procedure would fail to terminate, since neither termination condition would ever be true. This pointer-oriented view of cons, cdr, and car is cognitively rather different from the value-oriented view we have used before, but important for an understanding of list mutators.