[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4 Inheritance

4.4.1 Class hierarchy and inheritance of slots  
4.4.2 Instance creation and slot access  
4.4.3 Slot description  
4.4.4 Class precedence list  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.1 Class hierarchy and inheritance of slots

Inheritance is specified upon class definition. As said in the introduction, GOOPS supports multiple inheritance. Here are some class definitions:

 
(define-class A () a)
(define-class B () b)
(define-class C () c)
(define-class D (A B) d a)
(define-class E (A C) e c)
(define-class F (D E) f)

A, B, C have a null list of super classes. In this case, the system will replace it by the list which only contains <object>, the root of all the classes defined by define-class. D, E, F use multiple inheritance: each class inherits from two previously defined classes. Those class definitions define a hierarchy which is shown in Figure 1. In this figure, the class <top> is also shown; this class is the super class of all Scheme objects. In particular, <top> is the super class of all standard Scheme types.

 
hierarchy
Fig 1: A class hierarchy

The set of slots of a given class is calculated by taking the union of the slots of all its super class. For instance, each instance of the class D, defined before will have three slots (a, b and d). The slots of a class can be obtained by the class-slots primitive. For instance,

 
(class-slots A) => ((a))
(class-slots E) => ((a) (e) (c))
(class-slots F) => ((e) (c) (b) (d) (a) (f))

Note: The order of slots is not significant.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.2 Instance creation and slot access

Creation of an instance of a previously defined class can be done with the make procedure. This procedure takes one mandatory parameter which is the class of the instance which must be created and a list of optional arguments. Optional arguments are generally used to initialize some slots of the newly created instance. For instance, the following form

 
(define c (make <complex>))

will create a new <complex> object and will bind it to the c Scheme variable.

Accessing the slots of the new complex number can be done with the slot-ref and the slot-set! primitives. Slot-set! primitive permits to set the value of an object slot and slot-ref permits to get its value.

 
(slot-set! c 'r 10)
(slot-set! c 'i 3)
(slot-ref c 'r) => 10
(slot-ref c 'i) => 3

Using the describe function is a simple way to see all the slots of an object at one time: this function prints all the slots of an object on the standard output.

First load the module (oop goops describe):

 
(use-modules (oop goops describe))

The expression

 
(describe c)

will now print the following information on the standard output:

 
#<<complex> 401d8638> is an instance of class <complex>
Slots are: 
     r = 10
     i = 3


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.3 Slot description

When specifying a slot, a set of options can be given to the system. Each option is specified with a keyword. The list of authorized keywords is given below:

To illustrate slot description, we shall redefine the <complex> class seen before. A definition could be:

 
(define-class <complex> (<number>) 
   (r #:init-value 0 #:getter get-r #:setter set-r! #:init-keyword #:r)
   (i #:init-value 0 #:getter get-i #:setter set-i! #:init-keyword #:i))

With this definition, the r and i slot are set to 0 by default. Value of a slot can also be specified by calling make with the #:r and #:i keywords. Furthermore, the generic functions get-r and set-r! (resp. get-i and set-i!) are automatically defined by the system to read and write the r (resp. i) slot.

 
(define c1 (make <complex> #:r 1 #:i 2))
(get-r c1) => 1
(set-r! c1 12)
(get-r c1) => 12
(define c2 (make <complex> #:r 2))
(get-r c2) => 2
(get-i c2) => 0

Accessors provide an uniform access for reading and writing an object slot. Writing a slot is done with an extended form of set! which is close to the Common Lisp setf macro. So, another definition of the previous <complex> class, using the #:accessor option, could be:

 
(define-class <complex> (<number>) 
   (r #:init-value 0 #:accessor real-part #:init-keyword #:r)
   (i #:init-value 0 #:accessor imag-part #:init-keyword #:i))

Using this class definition, reading the real part of the c complex can be done with:
 
(real-part c)
and setting it to the value contained in the new-value variable can be done using the extended form of set!.
 
(set! (real-part c) new-value)

Suppose now that we have to manipulate complex numbers with rectangular coordinates as well as with polar coordinates. One solution could be to have a definition of complex numbers which uses one particular representation and some conversion functions to pass from one representation to the other. A better solution uses virtual slots. A complete definition of the <complex> class using virtual slots is given in Figure 2.

 
 
(define-class <complex> (<number>)
   ;; True slots use rectangular coordinates
   (r #:init-value 0 #:accessor real-part #:init-keyword #:r)
   (i #:init-value 0 #:accessor imag-part #:init-keyword #:i)
   ;; Virtual slots access do the conversion
   (m #:accessor magnitude #:init-keyword #:magn  
      #:allocation #:virtual
      #:slot-ref (lambda (o)
                  (let ((r (slot-ref o 'r)) (i (slot-ref o 'i)))
                    (sqrt (+ (* r r) (* i i)))))
      #:slot-set! (lambda (o m)
                    (let ((a (slot-ref o 'a)))
                      (slot-set! o 'r (* m (cos a)))
                      (slot-set! o 'i (* m (sin a))))))
   (a #:accessor angle #:init-keyword #:angle
      #:allocation #:virtual
      #:slot-ref (lambda (o)
                  (atan (slot-ref o 'i) (slot-ref o 'r)))
      #:slot-set! (lambda(o a)
                   (let ((m (slot-ref o 'm)))
                      (slot-set! o 'r (* m (cos a)))
                      (slot-set! o 'i (* m (sin a)))))))

Fig 2: A <complex> number class definition using virtual slots

This class definition implements two real slots (r and i). Values of the m and a virtual slots are calculated from real slot values. Reading a virtual slot leads to the application of the function defined in the #:slot-ref option. Writing such a slot leads to the application of the function defined in the #:slot-set! option. For instance, the following expression

 
(slot-set! c 'a 3)

permits to set the angle of the c complex number. This expression conducts, in fact, to the evaluation of the following expression

 
((lambda o m)
    (let ((m (slot-ref o 'm)))
       (slot-set! o 'r (* m (cos a)))
       (slot-set! o 'i (* m (sin a))))
  c 3)

A more complete example is given below:

 
 
(define c (make <complex> #:r 12 #:i 20))
(real-part c) => 12
(angle c) => 1.03037682652431
(slot-set! c 'i 10)
(set! (real-part c) 1)
(describe c) =>
          #<<complex> 401e9b58> is an instance of class <complex>
          Slots are: 
               r = 1
               i = 10
               m = 10.0498756211209
               a = 1.47112767430373

Since initialization keywords have been defined for the four slots, we can now define the make-rectangular and make-polar standard Scheme primitives.

 
(define make-rectangular 
   (lambda (x y) (make <complex> #:r x #:i y)))

(define make-polar
   (lambda (x y) (make <complex> #:magn x #:angle y)))


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.4.4 Class precedence list

A class may have more than one superclass. (4) With single inheritance (one superclass), it is easy to order the super classes from most to least specific. This is the rule:

 
Rule 1: Each class is more specific than its superclasses.

With multiple inheritance, ordering is harder. Suppose we have

 
(define-class X ()
   (x #:init-value 1))

(define-class Y ()
   (x #:init-value 2))

(define-class Z (X Y)
   (...))

In this case, the Z class is more specific than the X or Y class for instances of Z. However, the #:init-value specified in X and Y leads to a problem: which one overrides the other? The rule in GOOPS, as in CLOS, is that the superclasses listed earlier are more specific than those listed later. So:

 
Rule 2: For a given class, superclasses listed earlier are more
        specific than those listed later.

These rules are used to compute a linear order for a class and all its superclasses, from most specific to least specific. This order is called the "class precedence list" of the class. Given these two rules, we can claim that the initial form for the x slot of previous example is 1 since the class X is placed before Y in class precedence list of Z.

These two rules are not always enough to determine a unique order, however, but they give an idea of how things work. Taking the F class shown in Figure 1, the class precedence list is

 
(f d e a c b <object> <top>)

However, it is usually considered a bad idea for programmers to rely on exactly what the order is. If the order for some superclasses is important, it can be expressed directly in the class definition.

The precedence list of a class can be obtained by the function class-precedence-list. This function returns a ordered list whose first element is the most specific class. For instance,

 
(class-precedence-list B) => (#<<class> B 401b97c8> 
                                     #<<class> <object> 401e4a10> 
                                     #<<class> <top> 4026a9d8>)

However, this result is not too much readable; using the function class-name yields a clearer result:

 
(map class-name (class-precedence-list B)) => (B <object> <top>) 


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Ingo Ruhnke on September, 12 2002 using texi2html