;;; -*- Mode:gate; Fonts:(HL12 HL12I HL12B CPTFONTB HL12BI HL12B HL12I ) -*- =Node: 4Flavor Families* =Text: 3FLAVOR FAMILIES* The following organization conventions are recommended for programs that use flavors. A 1base flavor* is a flavor that defines a whole family of related flavors, all of which have that base flavor as a component. Typically the base flavor includes things relevant to the whole family, such as instance variables, 2:required-methods* and 2:required-instance-variables* declarations, default methods for certain operations, 2:method-combination* declarations, and documentation on the general protocols and conventions of the family. Some base flavors are complete and can be instantiated, but most are not instantiatable and merely serve as a base upon which to build other flavors. The base flavor for the 1foo* family is often named 2basic-1foo**. A 1mixin flavor* is a flavor that defines one particular feature of an object. A mixin cannot be instantiated, because it is not a complete description. Each module or feature of a program is defined as a separate mixin; a usable flavor can be constructed by choosing the mixins for the desired characteristics and combining them, along with the appropriate base flavor. By organizing your flavors this way, you keep separate features in separate flavors, and you can pick and choose among them. Sometimes the order of combining mixins does not matter, but often it does, because the order of flavor combination controls the order in which daemons are invoked and wrappers are wrapped. Such order dependencies should be documented as part of the conventions of the appropriate family of flavors. A mixin flavor that provides the 1mumble* feature is often named 1mumble2-mixin**. If you are writing a program that uses someone else's facility to do something, using that facility's flavors and methods, your program may still define its own flavors, in a simple way. The facility provides a base flavor and a set of mixins: the caller can combine these in various ways depending on exactly what it wants, since the facility probably does not provide all possible useful combinations. Even if your private flavor has exactly the same components as a pre-existing flavor, it can still be useful since you can use its 2:default-init-plist* (see 4(FLAVOR-3)Defflavor Options*) to select options of its component flavors and you can define one or two methods to customize it ``just a little''. =Node: 4Vanilla Flavor* =Text: 3VANILLA FLAVOR* The operations described in this section are a standard protocol, which all message-receiving objects are assumed to understand. The standard methods that implement this protocol are automatically supplied by the flavor system unless the user specifically tells it not to do so. These methods are associated with the flavor 2si:vanilla-flavor*: 3si:vanilla-flavor* 1Flavor* Unless you specify otherwise (with the 2:no-vanilla-flavor* option to 2defflavor*), every flavor includes the ``vanilla'' flavor, which has no instance variables but provides some basic useful methods. 3:print-self* 1stream* 1prindepth* 1escape-p* 1Operation* The object should output its printed-representation to a stream. The printer sends this message when it encounters an instance or an entity. The arguments are the stream, the current depth in list-structure (for comparison with 2prinlevel*), and whether escaping is enabled (a copy of the value of 2*print-escape**; see 4(READPRINT-1)Options that Control Printing*). 2si:vanilla-flavor* ignores the last two arguments and prints something like 2#<1flavor-name** 1octal-address2>**. The 1flavor-name* tells you what type of object it is and the 1octal-address* allows you to tell different objects apart (provided the garbage collector doesn't move them behind your back). 3:describe* 1Operation* The object should describe itself, printing a description onto the 2*standard-output** stream. The 2describe* function sends this message when it encounters an instance. 2si:vanilla-flavor* outputs in a reasonable format the object, the name of its flavor, and the names and values of its instance-variables. 3:set* 1keyword* 1value* 1Operation* The object should set the internal value specified by 1keyword* to the new value 1value*. For flavor instances, the 2:set* operation uses 2:case* method combination, and a method is generated automatically to set each settable instance variable, with 1keyword* being the variable's name as a keyword. 3:which-operations* 1Operation* The object should return a list of the operations it can handle. 2si:vanilla-flavor* generates the list once per flavor and remembers it, minimizing consing and compute-time. If the set of operations handled is changed, this list is regenerated the next time someone asks for it. 3:operation-handled-p* 1operation* 1Operation* 1operation* is an operation name. The object should return 2t* if it has a handler for the specified operation, 2nil* if it does not. 3:get-handler-for* 1operation* 1Operation* 1operation* is an operation name. The object should return the method it uses to handle 1operation*. If it has no handler for that operation, it should return 2nil*. This is like the 2get-handler-for* function (see 4(FLAVOR-2)Flavor Functions*), but, of course, you can use it only on objects known to accept messages. 3:send-if-handles* 1operation* &rest 1arguments* 1Operation* 1operation* is an operation name and 1arguments* is a list of arguments for the operation. If the object handles the operation, it should send itself a message with that operation and arguments, and return whatever values that message returns. If it doesn't handle the operation it should just return 2nil*. 3:eval-inside-yourself* 1form* 1Operation* The argument is a form that is evaluated in an environment in which special variables with the names of the instance variables are bound to the values of the instance variables. It works to 2setq* one of these special variables; the instance variable is modified. This is intended to be used mainly for debugging. 3:funcall-inside-yourself* 1function* &rest 1args* 1Operation* 1function* is applied to 1args* in an environment in which special variables with the names of the instance variables are bound to the values of the instance variables. It works to 2setq* one of these special variables; the instance variable is modified. This is a way of allowing callers to provide actions to be performed in an environment set up by the instance. 3:break* 1Operation* 2break* is called in an environment in which special variables with the names of the instance variables are bound to the values of the instance variables. =Node: 4Method Combination* =Text: 3METHOD COMBINATION* When a flavor has or inherits more than one method for an operation, they must be called in a specific sequence. The flavor system creates a function called a 1combined method* which calls all the user-specified methods in the proper order. Invocation of the operation actually calls the combined method, which is responsible for calling the others. For example, if the flavor 2foo* has components and methods as follows: 3(defflavor foo () (foo-mixin foo-base))* 3(defflavor foo-mixin () (bar-mixin))* 3(defmethod (foo :before :hack) ...)* 3(defmethod (foo :after :hack) ...)* 3(defmethod (foo-mixin :before :hack) ...)* 3(defmethod (foo-mixin :after :hack) ...)* 3(defmethod (bar-mixin :before :hack) ...)* 3(defmethod (bar-mixin :hack) ...)* 3(defmethod (foo-base :hack) ...)* 3(defmethod (foo-base :after :hack) ...)* then the combined method generated looks like this (ignoring many important details not related to this issue): 3(defmethod (foo :combined :hack) (&rest args)* 3 (apply #'(:method foo :before :hack) args)* 3 (apply #'(:method foo-mixin :before :hack) args)* 3 (apply #'(:method bar-mixin :before :hack) args)* 3 (multiple-value-prog1* 3 (apply #'(:method bar-mixin :hack) args)* 3 (apply #'(:method foo-base :after :hack) args)* 3 (apply #'(:method foo-mixin :after :hack) args)* 3 (apply #'(:method foo :after :hack) args)))* This example shows the default style of method combination, the one described in the introductory parts of this chapter, called 2:daemon* combination. Each style of method combination defines which 1method types* it allows, and what they mean. 2:daemon* combination accepts method types 2:before* and 2:after*, in addition to 1untyped* methods; then it creates a combined method which calls all the 2:before* methods, only one of the untyped methods, and then all the 2:after* methods, returning the value of the untyped method. The combined method is constructed by a function much like a macro's expander function, and the precise technique used to create the combined method is what gives 2:before* and 2:after* their meaning. Note that the 2:before* methods are called in the order 2foo*, 2foo-mixin*, 2bar-mixin* and 2foo-base*. (2foo-base* does not have a 2:before* method, but if it had one that one would be last.) This is the standard ordering of the components of the flavor 2foo* (see 4(FLAVOR-2)Mixing Flavors*); since it puts the base flavor last, it is called 2:base-flavor-last* ordering. The 2:after* methods are called in the opposite order, in which the base flavor comes first. This is called 2:base-flavor-first* ordering. Only one of the untyped methods is used; it is the one that comes first in 2:base-flavor-last* ordering. An untyped method used in this way is called a 1primary* method. Other styles of method combination define their own method types and have their own ways of combining them. Use of another style of method combination is requested with the 2:method-combination* option to 2defflavor* (see 4(FLAVOR-3)Defflavor Options*). Here is an example which uses 2:list* method combination, a style of combination that allows 2:list* methods and untyped methods: 3(defflavor foo () (foo-mixin foo-base))* 3(defflavor foo-mixin () (bar-mixin))* 3(defflavor foo-base () ()* 3 (:method-combination (:list :base-flavor-last :win)))* 3(defmethod (foo :list :win) ...)* 3(defmethod (foo :win) ...)* 3(defmethod (foo-mixin :list :win) ...)* 3(defmethod (bar-mixin :list :win) ...)* 3(defmethod (bar-mixin :win) ...)* 3(defmethod (foo-base :win) ...)* yielding the combined method 3(defmethod (foo :combined :win) (&rest args)* 3 (list* 3 (apply #'(:method foo :list :win) args)* 3 (apply #'(:method foo-mixin :list :win) args)* 3 (apply #'(:method bar-mixin :list :win) args)* 3 (apply #'(:method foo :win) args)* 3 (apply #'(:method bar-mixin :win) args)* 3 (apply #'(:method foo-base :win) args)))* The 2:method-combination* option in the 2defflavor* for 2foo-base* causes 2:list* method combination to be used for the 2:win* operation on all flavors that have 2foo-base* as a component, including 2foo*. The result is a combined method which calls all the methods, including all the untyped methods rather than just one, and makes a list of the values they return. All the 2:list* methods are called first, followed by all the untyped methods; and within each type, the 2:base-flavor-last* ordering is used as specified. If the 2:method-combination* option said 2:base-flavor-first*, the relative order of the 2:list* methods would be reversed, and so would the untyped methods, but the 2:list* methods would still be called before the untyped ones. 2:base-flavor-last* is more often right, since it means that 2foo*'s own methods are called first and 2si:vanilla-flavor*'s methods (if it has any) are called last. A few specific method types, such as 2:default* and 2:around*, have standard meanings independent of the style of method combination, and can be used with any style. They are described in a table below. Here are the standardly defined method combination styles. 2:daemon* The default style of method combination. All the 2:before* methods are called, then the primary (untyped) method for the outermost flavor that has one is called, then all the 2:after* methods are called. The value returned is the value of the primary method. 2:daemon-with-or* Like the 2:daemon* method combination style, except that the primary method is wrapped in an 2:or* special form with all 2:or* methods. Multiple values can be returned from the primary method, but not from the 2:or* methods (as in the 2or* special form). This produces code like the following in combined methods: 3(progn (foo-before-method)* 3 (multiple-value-prog1* 3 (or (foo-or-method)* 3 (foo-primary-method))* 3 (foo-after-method)))* This is useful primarily for flavors in which a mixin introduces an alternative to the primary method. Each 2:or* method gets a chance to run before the primary method and to decide whether the primary method should be run or not; if any 2:or* method returns a non-2nil* value, the primary method is not run (nor are the rest of the 2:or* methods). Note that the ordering of the combination of the 2:or* methods is controlled by the 1order* keyword in the 2:method-combination* option. 2:daemon-with-and* Like 2:daemon-with-or* except that it combines 2:and* methods in an 2and* special form. The primary method is run only if all of the 2:and* methods return non-2nil* values. 2:daemon-with-override* Like the 2:daemon* method combination style, except an 2or* special form is wrapped around the entire combined method with all 2:override* typed methods before the combined method. This differs from 2:daemon-with-or* in that the 2:before* and 2:after* daemons are run only if 1none* of the 2:override* methods returns non-2nil*. The combined method looks something like this: 3(or (foo-override-method)* 3 (progn (foo-before-method)* 3 (foo-primary-method)* 3 (foo-after-method))) 2:progn** Calls all the methods inside a 2progn* special form. Only untyped and 2:progn* methods are allowed. The combined method calls all the 2:progn* methods and then all the untyped methods. The result of the combined method is whatever the last of the methods returns. 2:or* Calls all the methods inside an 2or* special form. This means that each of the methods is called in turn. Only untyped methods and 2:or* methods are allowed; the 2:or* methods are called first. If a method returns a non-2nil* value, that value is returned and none of the rest of the methods are called; otherwise, the next method is called. In other words, each method is given a chance to handle the message; if it doesn't want to handle the message, it can return 2nil*, and the next method gets a chance to try. 2:and* Calls all the methods inside an 2and* special form. Only untyped methods and 2:and* methods are allowed. The basic idea is much like 2:or*; see above. 2:append* Calls all the methods and appends the values together. Only untyped methods and 2:append* methods are allowed; the 2:append* methods are called first. 2:nconc* Calls all the methods and 2nconc*'s the values together. Only untyped methods and 2:nconc* methods are allowed, etc. 2:list* Calls all the methods and returns a list of their returned values. Only untyped methods and 2:list* methods are allowed, etc. 2:inverse-list* Calls each method with one argument; these arguments are successive elements of the list that is the sole argument to the operation. Returns no particular value. Only untyped methods and 2:inverse-list* methods are allowed, etc. If the result of a 2:list*-combined operation is sent back with an 2:inverse-list*-combined operation, with the same ordering and with corresponding method definitions, each component flavor receives the value that came from that flavor. 2:pass-on* Calls each method on the values returned by the preceeding one. The values returned by the combined method are those of the outermost call. The format of the declaration in the 2defflavor* is: 3(:method-combination (:pass-on (1ordering* . 1arglist*))* 3 . 1operation-names*)* where 1ordering* is 2:base-flavor-first* or 2:base-flavor-last*. 1arglist* may include the 2&aux* and 2&optional* keywords. Only untyped methods and 2:pass-on* methods are allowed. The 2:pass-on* methods are called first. 2:case* With 2:case* method combination, the combined method automatically does a 2selectq* dispatch on the first argument of the operation, known as the 1suboperation*. Methods of type 2:case* can be used, and each one specifies one suboperation that it applies to. If no 2:case* method matches the suboperation, the primary method, if any, is called. Example: 3(defflavor foo (a b) ()* 3 (:method-combination (:case :base-flavor-last :win)))* This method handles 3(send a-foo :win :a)*: 3(defmethod (foo :case :win :a) ()* 3 a)* This method handles 3(send a-foo :win :a*b)*: 3(defmethod (foo :case :win :a*b) ()* 3 (* a b))* This method handles 3(send a-foo :win :something-else)*: 3(defmethod (foo :win) (suboperation)* 3 (list 'something-random suboperation))* 2:case* methods are unusual in that one flavor can have many 2:case* methods for the same operation, as long as they are for different suboperations. The suboperations 2:which-operations*, 2:operation-handled-p*, 2:send-if-handles* and 2:get-handler-for* are all handled automatically based on the collection of 2:case* methods that are present. Methods of type 2:or* are also allowed. They are called just before the primary method, and if one of them returns a non-2nil* value, that is the value of the operation, and no more methods are called. Here is a table of all the method types recognized by the standard styles of method combination. 2(no type)* If no type is given to 2defmethod*, a primary method is created. This is the most common type of method. 2:before* 2:after*Used for the before-daemon and after-daemon methods used by 2:daemon* method combination. 2:default* If there are no untyped methods among any of the flavors being combined, then the 2:default* methods (if any) are treated as if they were untyped. If there are any untyped methods, the 2:default* methods are ignored. Typically a base-flavor (see 4(FLAVOR-4)Flavor Families*) defines some default methods for certain of the operations understood by its family. When using the default kind of method combination these default methods are suppressed if another component provides a primary method. 2:or* 2:and*Used for 2:daemon-with-or* and 2:daemon-with-and* method combination. The 2:or* methods are wrapped in an 2or*, or the 2:and* methods are wrapped in an 2and*, together with the primary method, between the 2:before* and 2:after* methods. 2:override* Allows the features of 2:or* method combination to be used together with daemons. If you specify 2:daemon-with-override* method combination, you may use 2:override* methods. The 2:override* methods are executed first, until one of them returns non-2nil*. If this happens, that method's value(s) are returned and no more methods are used. If all the 2:override* methods return 2nil*, the 2:before*, primary and 2:after* methods are executed as usual. In typical usages of this feature, the 2:override* method usually returns 2nil* and does nothing, but in exceptional circumstances it takes over the handling of the operation. 2:or, :and, :progn, :list, :inverse-list, pass-on, :append, :nconc.* Each of these methods types is allowed in the method combination style of the same name. In those method combination styles, these typed methods work just like untyped ones, but all the typed methods are called before all the untyped ones. 2:case* 2:case* methods are used by 2:case* method combination. These method types can be used with any method combination style; they have standard meanings independent of the method combination style being used. 2:around* An 2:around* method is able to control when, whether and how the remaining methods are executed. It is given a continuation that is a function that will execute the remaining methods, and has complete responsibility for calling it or not, and deciding what arguments to give it. For the simplest behavior, the arguments should be the operation name and operation arguments that the 2:around* method itself received; but sometimes the whole purpose of the 2:around* method is to modify the arguments before the remaining methods see them. The 2:around* method receives three special arguments before the arguments of the operation itself: the 1continuation*, the 1mapping-table*, and the 1original-argument-list*. The last is a list of the operation name and operation arguments. The simplest way for the 2:around* method to invoke the remaining methods is to do 3(lexpr-funcall-with-mapping-table* 3 1continuation* 1mapping-table** 3 1original-argument-list*)* In general, the 1continuation* should be called with either 2funcall-with-mapping-table* or 2lexpr-funcall-with-mapping-table*, providing the 1continuation*, the 1mapping-table*, and the operation name (which you know because it is the same as in the 2defmethod*), followed by whatever arguments the remaining methods are supposed to see. 3(defflavor foo-one-bigger-mixin () ())* 3(defmethod (foo-one-bigger-mixin :around :set-foo)* 3 (cont mt ignore new-foo)* 3 (funcall-with-mapping-table cont mt :set-foo* 3 (1+ new-foo)))* is a mixin which modifies the 2:set-foo* operation so that the value actually used in it is one greater than the value specified in the message. 2:inverse-around* 2:inverse-around* methods work like 2:around* methods, but they are invoked at a different time and in a different order. With 2:around* methods, those of earlier flavor components components are invoked first, starting with the instantiated flavor itself, and those of earlier components are invoked within them. 2:inverse-around* methods are invoked in the opposite order: 2si:vanilla-flavor* would come first. Also, all 2:around* methods and wrappers are invoked inside all the 2:inverse-around* methods. For example, the 2:inverse-around* 2:init* method for 2tv:sheet* (a base flavor for all window flavors) is used to handle the init keywords 2:expose-p* and 2:activate-p*, which cannot be handled correctly until the window is entirely set up. They are handled in this method because it is guaranteed to be the first method invoked by the 2:init* operation on any flavor of window (because no component of 2tv:sheet* defines an 2:inverse-around* method for this operation). All the rest of the work of making a new window valid takes place in this method's continuation; when the continuation returns, the window must be as valid as it will ever be, and it is ready to be exposed or activated. 2:wrapper* Used internally by 2defwrapper*. Note that if one flavor defines both a wrapper and an 2:around* method for the same operation, the 2:around* method is executed inside the wrapper. 2:combined* Used internally for automatically-generated 1combined* methods. The most common form of combination is 2:daemon*. One thing may not be clear: when do you use a 2:before* daemon and when do you use an 2:after* daemon? In some cases the primary method performs a clearly-defined action and the choice is obvious: 2:before :launch-rocket* puts in the fuel, and 2:after :launch-rocket* turns on the radar tracking. In other cases the choice can be less obvious. Consider the 2:init* message, which is sent to a newly-created object. To decide what kind of daemon to use, we observe the order in which daemon methods are called. First the 2:before* daemon of the instantiated flavor is called, then 2:before* daemons of successively more basic flavors are called, and finally the 2:before* daemon (if any) of the base flavor is called. Then the primary method is called. After that, the 2:after* daemon for the base flavor is called, followed by the 2:after* daemons at successively less basic flavors. Now, if there is no interaction among all these methods, if their actions are completely independent, then it doesn't matter whether you use a 2:before* daemon or an 2:after* daemon. There is a difference if there is some interaction. The interaction we are talking about is usually done through instance variables; in general, instance variables are how the methods of different component flavors communicate with each other. In the case of the 2:init* operation, the 1init-plist* can be used as well. The important thing to remember is that no method knows beforehand which other flavors have been mixed in to form this flavor; a method cannot make any assumptions about how this flavor has been combined, and in what order the various components are mixed. This means that when a 2:before* daemon has run, it must assume that none of the methods for this operation have run yet. But the 2:after* daemon knows that the 2:before* daemon for each of the other flavors has run. So if one flavor wants to convey information to the other, the first one should ``transmit'' the information in a 2:before* daemon, and the second one should ``receive'' it in an 2:after* daemon. So while the 2:before* daemons are run, information is ``transmitted''; that is, instance variables get set up. Then, when the 2:after* daemons are run, they can look at the instance variables and act on their values. In the case of the 2:init* method, the 2:before* daemons typically set up instance variables of the object based on the init-plist, while the 2:after* daemons actually do things, relying on the fact that all of the instance variables have been initialized by the time they are called. The problems become most difficult when you are creating a network of instances of various flavors that are supposed to point to each other. For example, suppose you have flavors for ``buffers'' and ``streams'', and each buffer should be accompanied by a stream. If you create the stream in the 2:before* 2:init* method for buffers, you can inform the stream of its corresponding buffer with an init keyword, but the stream may try sending messages back to the buffer, which is not yet ready to be used. If you create the stream in the 2:after* 2:init* method for buffers, there will be no problem with stream creation, but some other 2:after* 2:init* methods of other mixins may have run and made the assumption that there is to be no stream. The only way to guarantee success is to create the stream in a 2:before* method and inform it of its associated buffer by sending it a message from the buffer's 2:after* 2:init* method. This scheme--creating associated objects in 2:before* methods but linking them up in 2:after* methods--often avoids problems, because all the various associated objects used by various mixins at least exist when it is time to make other objects point to them. Since flavors are not hierarchically organized, the notion of levels of abstraction is not rigidly applicable. However, it remains a useful way of thinking about systems.