Recall our dog lecture: note that with classes and methods we can either replace functionality (a method that is special and does something totally different than the method) or we can do something slightly different (using next-method to do what would have happened, before or after doing something else). Today, look at simple example of single inheritance and generic functions in more detail. OOP is a technique for organizing "large" systems. (Note: this makes it hard for us to get across in lecture many of the advantages -- we can look at the techniques here, but much of the real learning will come in problem set #5, which is a large system.) Builds on the notion of generic operations that we covered earlier in the semester. Conceptually a table of procedures, indexed by operations and data types. In Swindle we use a generic functions style of type dispatching. The NEW IDEAS in OOP (beyond generic functions) are: 1) objects have local state variables (called slots), which are only accessible in certain contexts -- based on the type of the parameter/variable that names an object 2) types of objects (classes) are arranged in a hierarchy, from which they inherit their "behavior" (their slots and functions). A key principle in OOP is that a subclass should build on and modify the behavior of its superclass(es). Thus the central design issue in an OOP style of programming is to come up with a hierarchy of classes such that the objects of each class can build on the behavior of their superclasses (the idea is to have an effective way of SHARING code between objects of different classes). Another example, similar to last time recall syntax of defclass.... ==> (defclass () (location :type :initarg :location :initvalue 'here :accessor location) (speed :type :initarg :speed :initvalue 0 :accessor speed)) ==> (defclass () (fuel :type :initarg :fuel :initvalue 0 :accessor fuel)) ==> (defclass () (fuel :type :initarg :fuel :initvalue 0 :accessor fuel) (altitude :type :initarg :altitude :initvalue 0 :accessor altitude)) ==> (defclass ()) Note: single inheritance, so class hierarchy is a tree | | |------------------- | | | ==> (defgeneric (accelerate v a)) ;; A1 ==> (defmethod (accelerate (v ) (a )) ;; also possible, (set-speed! v (+ (speed v) a))) (set! (speed v) (+ (speed v) a))) ;; A2 ==> (defmethod (accelerate (r ) (a )) (cond ((> (fuel r) 0) (print "Whoosh...") (call-next-method)) (else: (print "No gas.")))) ;; A3 ==> (defmethod (accelerate (b ) (a )) (print "Dream on.")) Some things to note about this generic function o A method can access only those slots of a class and its superclasses. For instance, the first method (A1) can access the speed slot, of , as can the second method (A2). But the first method cannot access the fuel slot of . This is because is a superclass of . o The second function uses call-next-method -- which is valid because there is a next function (the one for ). The "next" function is defined solely by the class hierarchy. Now we can create some instances of objects of these classes, and call accelerate. ==> (define schwinn (make )) ==> (define saturn-v (make :fuel 10 :altitude 1000)) ==> (define chevy (make :fuel 10 :location 'there)) Each instance has all of the slots of the given class and its superclasses. Thus, schwinn has slots: location= here, speed=0 saturn-v has slots: location=here, speed=0,fuel=10,altitude=100000 chevy has slots: location=there, speed=0, fuel=10 [Note: this question is the prelim 2 equivalent of "what is the value of this expression".] If we evaluate (accelerate chevy 10) The most specific applicable function of the generic is the one for (because there is no method for class , and the next most specific class is for which there is a method). This function increments the speed slot by 10. If we evaluate (accelerate schwinn 10) The most specific method is the one for which simply prints out the string "Dream on" If we evaluate (accelerate saturn-v 10) The most specific method is the one for which checks if the fuel level is > 0 (which it is for this instance) and if so print "Whoosh" and then calls call-next-method. Recall, call-next-method means re-invoke the generic (in this case accelerate) with the same arguments, but as if the currently running function were *not* part of the generic function(that is run the next most specific function; the one that would have run if the current function had not been part of the generic function). The above example illustrates three different means of using generic functions and the class hierarchy to extend or modify the behavior of superclasses. o A class can simply inherit the behavior of its superclass (e.g., above an instance of class simply inherits the behavior of a ) o A class can replace the behavior of its superclass, by defining a class-specific method. This is sometimes called shadowing the behavior of the superclass (e.g., above an instance of class has a completely different behavior than the superclass ) o A class can modify the behavior of its superclass, by defining a class-specific method that uses next-method (e.g., above an instance of class has a behavior that modifies that of a general ). There are 3 common patterns of such modified behavior: a BEFORE method does some computation and then a next-method call an AFTER method does a next-method call and then some computation an AROUND method does computation, next-method, more computation Summary: each instance has its own copy of slots of given class and all its superclasses. call-next-method gives ability to build on/modify behavior of superclasses.