Today: two more (last) OO topics Method Specialization -- method ordering for several arguments Multiple Inheritance -- classes with more than one superclass Reminder: PS#4 due Today. PS#5 out today or tomorrow. You have about 3 weeks to do it. You may need it! Method specialization -- What about when several parameters to a generic function are classes, what is the method ordering rule? Consider the following example (another Prelim #2 easy question): (defclass ()) (defclass ()) (defgeneric (whack a b)) (defmethod (whack (a ) (b )) (echo "Animate")) (defmethod (whack (a ) (o )) (echo "Person") (call-next-method)) (defmethod (whack (a ) (b )) (echo "Animate, animate") (call-next-method)) (defmethod (whack (a ) (b )) (echo "Person, person") (call-next-method)) Remember that a method of a generic function is APPLICABLE to a given list of arguments if each successive parameter of the function matches the corresponding argument (where a parameter MATCHES an argument if it is of a compatible type -- it is the class of or a superclass of the given argument). Given two applicable methods, one is MORE SPECIFIC than the other if in left to right order it is the first method to have a more specific parameter (closer in the class hierarchy to the given argument). This specifies the ordering of (applicable) methods, in terms of more specific to less specific. Thus the meaning of "next" function in next-method is the next most specific function. Consider the following instances of the above classes: (define p1 (make )) (define p2 (make )) (define a1 (make )) (define a2 (make )) If we call the generic whack (whack p1 p2) all of the functions of this generic are applicable to the two given arguments. What order do they run in? Person, person Person Animate, Animate Animate left to right order, until one parameter differs in specificity from the corresponding parameter of the other function. Similarly, (whack p1 a1) Person Animate, animate Animate (whack a1 p1) Animate, animate Animate (whack p1 'foo) Person Animate (whack a1 ) Animate Note: in general, in OOP the order in which you TYPE stuff doesn't matter. Order of add-method for a generic function, order of slots in a define-class, keyword arguments to make. Exception: when you overwrite something's previous value; e.g., using defmethod to add "another" method with exactly the same types of parameters redefines this method (kind of like DEFINE -- works by side-effect). [Another exception is, of course, the order of parameters OF functions and arguments TO functions. But, this is true throughout Swindle, and not something peculiar to OOP.] We have seen the basic mechanism of object oriented programming, a class hierarchy specifies which function(s) of a generic are applicable to an instance of some class, and the order in which those function(s) apply. Going up the class hierarchy the functions become less specific (later in the ordering). In single inheritance there is a linear order from a given class to the root (specified by the single direct superclass, or parent, of each class). In multiple inheritance, a class can have more than one direct superclass. Idea: sometimes it makes sense for a given class to inherit code/slots from more than one direct superclass. This can be a very effective means of combining behaviors of different classes. It is difficult to illustrate using a small example, but in PS5 there are classes that combine the behavior of other classes. With very little additional code, powerful new behaviors can be obtained. The use of multiple inheritance leads to an inheritance structure that is a directed acayclic graph (DAG) (each class pointing to its direct superclasses) rather than a tree. There is no longer a linear order from a given class to the root of the tree (which defines, for example, the ordering of methods). This can make things a bit more confusing. Consider the following class definitions: (defclass () (name :type )) (defclass () (weight :type :initvalue 140)) (defclass ()) (defclass () (patience :type :initvalue .00001)) (defclass () (iq :type :initvalue 10)) (defclass ( ) (bugs :type :initvalue 157)) which form the following class hierarchy animate / | \ / | \ / | \ / | \ person staff dean | / | / moron / | / |/ swindle-hacker [Note: arrows point "up" to superclass] Now if we make an instance of a , it has slots from all of its superclasses: (define billg (make )) it has slots: name, weight, patience, iq, bugs: (slot-ref billg 'name) --> # (slot-ref billg 'weight) --> 140 (slot-ref billg 'iq) --> 10 (slot-ref billg 'patience) --> 1e-05 (slot-ref billg 'bugs) --> 157 Note: in Swindle, it is a *bad* idea to have different classes with slots by the same name that mean different things. In single inheritance this was easier to avoid than in multiple inheritance. Now the class hierarchy is no longer a tree: there is branching going back up the hierarchy as well as going down it. Now the class hierarchy is a directed graph rather than a tree. This makes it impossible to define a unique ordering of classes from a given class back to the root. When the hierarchy was restricted to a tree, the ordering was just the path back to the root, but now there is not necessarily a unique such path. For instance from there are three different paths back to the root. What should the ordering be in this case? Not clear, could be: [moron, person, staff, animate], or [moron, staff, person, animate], or [staff, moron, person, animate]. (note that [staff, animate] is *not* a good path!) Now there is only a PARTIAL ORDERING of superclasses, not a TOTAL ORDER as occurred when there was a linear ordering of the super classes of a given class (with an inheritance TREE). Some object oriented languages specify some rule for imposing a total ordering on the superclasses, but this is almost never the "right" rule. Other languages give the programmer the ability to specify the ordering for a given class. In Swindle, the ordering is only defined by climbing up left branches before righ branches, but in a topological order. So you can tell precisely what order methods will be run in using next-method with multiple inheritance, but this is *BAD* when relying on cases that are not obvious: in these cases you should only rely on the fact that certain methods will happen before others, based on their location in the hierarchy (the PARTIAL ordering). So (punch billg) runs the most specific applicable method (or methods, if one calls next-method). The order of specificity is that first we look for a swindle-hacker method, last we look for an animate method, and we look for a moron method before we look for a person method, and both person and staff methods before we look for animate. The exact ordering of specificity is *defined*, but we don't want to use any information except the above constrains. The same issue arises in (next-method). If the (punch ) handler calls (call-next-method), and there are handlers for (punch ) and (punch ), ONE will be invoked, but *which* one is not obvious. Graphs: nodes and edges. Partial order (from a directed graph); a link from A to B means B < A, so B occurs before A. swindle-hacker < moron swindle-hacker < staff moron < person person < animate staff < animate dean < animate A *topological sort* of a DAG (why do we need it to be acyclic?) produces an order where if B < A then B occurs before A. Sample (BFS): swindle-hacker, moron, dean, staff, person, animate [actually, it's usually easier to produce these in the opposite order, with the topmost node first] Another sample: swindle-hacker, dean, staff , moron , person, animate Summary OOP used for organizing large systems, by arranging objects in a hierarchy, and sharing code through inheritance. Multiple inheritance is a way to combine the behaviors of different types of objects. If there is some more time: Classes in Java: Single Inheritance: slot access optimization To get a bit more flexibility in non-problematic cases: interfaces that are basically slot-less classes. The same is built into MzScheme (so Swindle is a much more sophisticated implementation). Mixins: This is enabled by multiple inheritance: designing a class that is supposed to be mixed into other classes -- providing some funcionality. MOP in CLOS: Swindle is an implementation of the Common Lisp Object System (CLOS). One main feature of this object system is that it has a Meta Object Protocol (MOP) -- this means that classes are themselves instances of "meta-classes" so we get operations like class-name and class-cpl: (map class-name (class-cpl )) --> ( ) This is a simple thing that is also part of Java (called "Reflection"). But CLOS allows much more: if classes specify behavior of their instances, then meta-classes specify behavior of classes --- in other words, meta-classes have methods that define how the object system behave! This means that you can use Swindle to play with completely different object systems with differen rules. (info in the tiny-clos file).