REDUCE

19.4 Using Inheritance

Inheritance is a mechanism that allows a flavor to be defined in terms of other flavors. This allows objects to be created that are specializations or generalizations of other objects. The object package supports what is called single inheritance – only one flavor can be inherited from by another flavor. The flavor being inherited, however, could have inherited from one other flavor, and so on. Note that the LISP machine supports multiple inheritance allowing more than one flavor to be inherited from at a time.

The best way to explain single inheritance is through an example:
Suppose we have defined the flavor mother and then define the flavor daughter in which we specified the inheritance-flavor-list to be mother. The inheriting process causes daughter to contain all the instance variables as well as the methods that were defined for mother. That is,a daughter object can deal with any of the instance variables and receive any of the messages defined for flavor mother without having to recreate them.

We refer to flavor mother as the parent of flavor daughter, and daughter as a child of mother. daughter, in turn, can be the parent of other flavors. Each child inherits the instance variables and methods of its parent, its parent’s parent, and so on.

When inheriting instance variables, a child flavor has as its instance variables the union of all those defined for its parents and those specifically defined for the new flavor. As a result, it would not make sense to give a child flavor an instance variable with the same name as one in one of its parent’s. For this reason, duplicate instance variables are flagged as an error.

Inheriting methods, however, is different. It is possible to define a method in a child flavor with the same name as a method in one of the parents. The effect is to override the parent’s method with the child’s method, as far as the child flavor is concerned. In other words, when sending a message to an object, the method executed is the first one found by searching backward from it through its parents. First the methods defined for the child flavor are searched, then the child’s parent’s methods, and so on until one is found.

As an example of overriding methods, the following code defines a flavor geometric-object. Then, a new flavor square is created with geometric-object as its parent. Finally, a flavor colored-square is created with square as its parent.

(defflavor geometric-object  
  (name  
   size)  
  ()                    % A flavor with no parents  
  )  
 
(defmethod (geometric-object display) ()  
  (printf "%w is %w units big" name size))  
 
(defmethod (geometric-object set-name) (new-name)  
  (setf name new-name))  
 
(defflavor square  
  (side-length)  
  (geometric-object)    % A flavor with one parent, geometric-object  
  )  
 
% Overriding for square the inherited method display  
 
(defmethod (square display) ()  
  (printf "%w is a square, %w units long on all sides" name  
          side-length))  
 
(defmethod (square new-length) (length)  
  (setf side-length length)  
  (setf size (⋆ length length)))  
 
(defflavor colored-square  
  (color)  
  (square)  
  )  
 
(defmethod (colored-square get-color) ()  
  color)

After the definitions, flavor colored-square has the instance variables:

        color  
        side-length  
        name  
        size

and has the methods:

19.4.1 Warning on Inheritance Usage

The Common lisp objects package will be incompatible with the existing objects package as far as inheritance is concerned. In Common lisp, an inherited instance variable cannot be accessed by simply referring to it within a method. Also, the way variables are initialized is quite different.

To access inherited instance variables, you must do the equivalent of sending the object a message to get the instance variable’s value. For example, referring to the instance variable banana in a method for the flavor fruit, a reference to banana in PSL becomes something like (=> self banana) in Common lisp. When you reference inherited instance variables in PSL and performance is important, you should clearly mark them. If performance is not an issue, make the instance variables gettable and refer to them by their method. This also applies to settable instance variables.

Uses of the INIT method may also have to be changed because the new objects package performs object initialization in a different way. Avoid the INIT method if you can, if you can’t mark it for change for Common lisp objects.

In this way, transition to Common lisp will be much easier.

19.4.2 Using SELF and MYSELF with Inheritance

Suppose an additional method was defined for flavor geometric-object:

  (defmethod (geometric-object display-me) ()  
  (=> self display))

Now suppose a display-me message is sent to an object of type square. Which method for the message ’display’ should be used by display-me? Should it look up the method in the context of the flavor where the method was defined (geometric-object) or the context of the flavor that received the original message (square)? The answer is to provide a new symbol, MYSELF. MYSELF means to look up messages in the context of the object that received the original message. SELF looks up methods in the context of the flavor it is defined in. If display-me was rewritten as:

(defmethod (geometric-object display-me) ()  
  (=> self display)  
  (=> myself display))

and sent to an object of flavor square, then the first display would be that defined in geometric-object, and the second would be the one defined for square. If the display-me message was sent to an object of flavor geometric-object, self and myself would be identical.

19.4.3 Inheritance and Initialization

You may have noticed that there is ambiguity in the order a newly created object is initialized when the object uses inheritance. When inheritance is used, initialization is performed as describe above with the following additions:

When the son flavor’s (i.e., the flavor given in the make-instance or instantiate-flavor call) DEFAULT-INIT method is executed, the first thing it does is call its father’s DEFAULT-INIT method. The second thing it does is execute its father’s INIT method, if it exists.

Notice that if the father flavor used inheritance, its DEFAULT-INIT method would first call its grandfather’s DEFAULT-INIT and INIT methods, and so on.

19.4.4 Making Changes to Inherited Code

The inheritance scheme used in this facility is static – all of the work is done at compile-time. This has some implications for compiling object definitions that use inheritance: