Search code examples
schemelisp

Exercise 12.10 from the book Scheme and the art of programming


The exercise 12.10, in the chapter about object oriented programming We build a queue data structure by make a function queue-maker. In this function, we handle the messages that send to the object for example, when we send the object the message enqueue! and the operand "a" => "a" will be push to the queue

(define q (queue-maker))
(q 'enqueue! 'a)

What is need to do is add handler for the message enqueue-list! with the operand will be a list. That will add all the items in the list to the queue

(q 'enqueue-list! '(b c d))

The question here is why we don't use the append! function to modify the queue?


Solution

  • Using append! is a disaster for either implementation of queues given in the book.

    Exercise 12.10 asks you to use the version defined in Program 12.15. This is the version that uses a tail pointer to make enqueue! efficient. There are two ways you could use append! in this implementation:

    1. You could append! to the queue list directly. This (a) does not make use of the tail pointer and thus has to walk the whole queue and, worse, (b) does not update the tail-pointer, trashing the data structure, so that the next time you call enqueue! you will lose everything you have just added. You could repair this by then updating the tail pointer to point at the new last cons which negates any supposed performance advantage of using append!. But this is not safe anyway, see below.
    2. You could append! to the tail pointer which points at the last cons of the queue. This is better, but you still must remember to update the tail pointer to point to the new last cons, so you still must walk the list you are appending. However this is still not safe, see below.

    Approach (1) both negates the advantage of using the tail pointer and is painful to implement. Approach (2) looks a bit better. But, again, see below, neither are safe.

    If you use the earlier implementation of queues, the one in program 12.13, it looks like using append! would work: there's no tail pointer to worry about now. But this also is unsafe.

    Here's why all these approaches are unsafe.

    Consider this code:

    (define (enqueue-list-twice q l)
      (q 'enqueue-list! l)
      (q 'enqueue-list! l) ;or just enqueue! anything
      l)
    

    What happens if you use append! to implement enqueue!? Well, the first time you do this you end up with a queue whose tail is l. It's not a copy of l, it is l. The second time you do this then append! will smash the last cons of this list, and hence the last cons of l, to be ... l.

    So this approach has set a nasty trap for you: it's stashed away one of its arguments as part of the queue, which will then be destructively modified. That's nasty.

    You can fix even this by making an explicit copy of the list, which, again, negates the advantage of using append! since you have to walk it to copy it anyway. By the time you've done all this it really is better just to implement the thing without having to second-guess append! all the way through. The obvious, safe, approach is just to implement enqueue-list! by repeatedly calling enqueue!.