next up previous contents
Next: Method Combination Up: Multiple Inheritance Previous: Shared Object Parts

The Object Precedence List Representation

 

In section gif I introduced the precedence list representation of objects in the single-inheritance case. In this section I will discuss a similar representation when using multiple inheritance in the class hierarchy. The precedence list representation is inspired by the class precedence lists from the Common Lisp Object System (CLOS) [3].

A class precedence list of a class C is a total ordering of C and all its (direct and indirect) superclasses. In this ordering of classes, each of the classes occurs exactly once. If C is a class with the direct super classes , , ... (in this order), the following rules define a class precedence list:

  1. C precedes , , ... and in the ordering, and
  2. precedes , for i from 1 to n-1.
For more details about class precedence lists, see [3].

An object precedence list is similar to a class precedence list. I.e., an object precedence list of an instance of a class C is the similar total ordering of the object parts of the instance, where each part occurs only once. The same constraints as described in the two points above should also hold for the object parts.

Let me illustrate class precedence lists and object precedence lists with the example in figure gif.

  
Figure: A sample class hierarchy with multiple inheritance.

For each class in the figure, precedence lists of the components (classes or instances) are shown in boxes. Considering for example the class F, an instance of F consists of the following ordered collection of parts:

A is the last one, because it must be preceded by both B and C according to (two applications of) rule number one from above; B precedes C because of rule number two; F is the first one, again because of rule number one. Using a precedence list representation of objects, an instance of the class F is represented as the list

where each part is a reference to the dispatch procedure of the part.

It is worth noticing, that using this representation of objects, the method lookup procedure in the multiple-super-class case is the same as in the single-super-class case. The method lookup procedure which applies has already been shown in section gif. Compared with the version of method-lookup from section gif (the so-called simple approach), in which the search strategy is encoded into the lookup procedure, the procedure shown in section gif is clearly a simplification. However, there is of course a price for this simplicity. The price is so to speak paid in class-handle , which is described next.

The procedure class-handle makes and returns a precedence list representation of an object. Recall that class-handle is used directly in the class definition template, and it is used in the procedure instantiate-super-class to fake the instantiation of an object part from already existing parts.

 

(define (class-handle self super-list)
  (cons self (merge-parts super-list)))

This procedure reflects rule number one in the definition of the class precedence list. Self is supposed to be a dispatch procedure, and super-list is a list of object precedence lists of the super parts of the object. The procedure must return an object precedence list of the object, to which self belongs.

The procedure merge-parts reflects rule number two in the definition of the class precedence list. Merge-parts is defined through an iterative (tail-recursive) helping procedure called merge-parts-1 .

     

(define (merge-parts lists)
  (reverse (merge-parts-1 () lists))) 

(define (merge-parts-1 result input)
  (if (null? input)
      result
      (let* ((first-list (car input))
             (first-el (car first-list))
             (rest-lists (cdr input)))
        (merge-parts-1
         (if (multi-member object-eq? first-el rest-lists)
             result
             (cons first-el result))
         (if (cdr first-list)
             (cons (cdr first-list) rest-lists)
             rest-lists)))))

(define (object-eq? x y)
  (eq? (class-of-object x) (class-of-object y)))

It is probably easiest to understand these merge procedures through an example of the merge process. Let us assume that we are about to instantiate an H object, relative to figure gif. In order to do that the three precedence lists of the super parts of H have to be merged. I.e., input to the procedure merge-parts-1 becomes:

Merge-parts-1 first checks if is a member of either of the lists ( ) or ( ). It is not, and therefore is included into result . Next, it is checked if is part of the two lists ( ) or ( ). That is the case, and therefore this instance of is ``postponed'' until it is met later during the merge process. Continuing this way, the result of merging the three lists becomes ( ).gif

The remaining procedures are multi-member and, in turn, its helping procedures. Multi-member tests whether an element is a member of a list, in a list of lists. The following implementation of multi-member just maps the member -like procedure, called memb , over the list of lists, and or -reduces the result to an boolean value:

 

(define (multi-member comparison el list-of-lists)
  (reduce or-proc
          (map (lambda (l)
                 (memb comparison el l))
               list-of-lists)
          #f))

Reduce is well-known, and or-proc is or , just in procedure form (in Scheme it is not possible to use a special form as a parameter to a higher order procedure). For the sake of completeness, these procedures are shown next:

   

(define (reduce combiner list initial-value)
  (if (null? list)
      initial-value
      (combiner (car list)
                (reduce combiner
                        (cdr list)
                        initial-value))))

(define (or-proc x y)
  (or x y))

This concludes the description of how to construct the precedence list representation of objects in the case of multiple super classes.



next up previous contents
Next: Method Combination Up: Multiple Inheritance Previous: Shared Object Parts



Kurt Noermark
Wed Mar 6 10:30:05 MET 1996