21.

21.1
The flavor system is basically a set of conventions and mechanisms for organizing programs. It provides a good way to organize complex programs built out of many parts, and helps to define the interfaces between the parts cleanly. Flavors are based on the object-oriented-programming ideas of Simula and Smalltalk, with some additional new ideas about modularity. Some of the basic concepts of the flavor system are:
ObjectsThe world is viewed as containing a number of objects . Each object has several operations which may be performed upon it. An object contains some state which it remembers; operations frequently have side-effects upon the state as well as upon other, related, objects in the world.
Message-Passing
The description of how an operation is to be performed on an object is embedded inside that object. This is in distinction to the traditional functional organization, in which there is a program for each operation which contains a conditional to select the behavior based on the type of the object. This is only a difference in convention; the two organizations are really the same except for the way they are indexed.
TypesStructure is imposed upon this world through the idea of types . Several objects can be said to have the same type if they behave the same with respect to their operations. This idea is actually quite fuzzy, as the behavior of an object is affected by its state and by its relations with other objects, as well as by its type. In implementation terms, two objects have the same type if their behavior is implemented by the same description, or program. Another way that the idea of type is fuzzy is that two objects can have the same type with respect to some aspects of their behavior, while differing in other aspects.
Type Combination
New types, that is, new repertoires of object behavior, can be constructed by combining existing types. The flavor system's main contribution is the provision of mechanisms by which this combination may be done flexibly and painlessly. The required amount of unmodular knowledge of the internal details of the types being combined is minimized.
Here are some of the jargon words used by the flavor system.
FlavorA flavor is a description of the nature and behavior of objects. The word flavor is used rather than type , class , category , or set to avoid the pre-existing connotations of those words, and to distinguish flavors from other implementations of similar ideas also present in the Lisp machine system.
Mixin FlavorA flavor need not be a complete description. A mixin flavor describes only a single aspect of objects' behavior. It is a module which can be combined with other flavors to produce a complete description. The resulting combination is itself a flavor.
Base FlavorTypes can themselves be organized by types. There may be many types of objects, all of which have certain operations in common and are directed at the same basic goal; they can be said all to belong to the same base type . A base flavor is a description of what those many types have in common, and what are the conventions they share.
MethodA method is a function which implements a certain operation on objects of a certain flavor. Like all Lisp functions, it takes arguments, returns results, and may have side-effects. An important part of the flavor system is the mechanism for combining methods for the same operation that come from different flavors. Thus a single method is not responsible for the whole of an operation; it only takes care of its flavor's portion of the operation.
MessageA message name is a symbol which is the name of an operation. Performing an operation on an object is done by "sending a message" to that object. This is implemented as calling the object as a function, with the first argument the message (or operation) name, and the succeeding arguments the arguments to the message. The flavor system uses the message symbol to find and invoke the appropriate method or methods, passing them the arguments. Message symbols are typically interned in the keyword package, thus they are prefixed with colons.
InstanceInstances are the implementation of the notion of object. A flavor may be instantiated ; this produces an object whose nature and behavior are described by that flavor. A flavor may be instantiated any number of times, producing any number of distinct objects with similar behavior.
Instance-Variable
The state of an object is maintained as the values of a set of instance variables , names for the parts of an object's state. Inside a method, the instance variables appear as Lisp variables which may be used and setq 'ed. Everything private to a particular instance is in the instance variables and everything shared between instances of the same flavor is part of the flavor.
InitializationInitialization , usually abbreviated init , is an operation which is performed on an object when it is created. This involves complex issues and will be explained later.
It is important to understand the notion of flavor combination . A flavor is typically constructed out of several other flavors, called its components . A flavor has some methods and instance variables of its own, and inherits others from its components. The components are combined in a particular order, specified when you define the flavor. Depth-first ordering is used; that is, if the components of flavor-1 specified by its definition are flavor-2 and flavor-3 , and the components of flavor-2 are flavor-2a and flavor-2b , then flavor-2a and flavor-2b come before flavor-3 in flavor-1 's fully-expanded list of components. Flavors earlier in the list of components are thought of as higher-level, more outside, more in control, or less basic than those later in the list of components. For uniformity, a flavor is always the first in its own list of components. Thus the first step in combining flavors is constructing the expanded list of components, so that all flavors which contribute in any way to the flavor being defined are enumerated, and their order is known. The remaining steps are instance-variable combination and method combination . (Certain minor flavor features, such as the default-init-plist, are also combined. But this doesn't involve any non-obvious issues.) Instance-variable combination is simple; instance variables with the same name are the same. Thus different components of a flavor may communicate through shared instance variables. Typically one component flavor is "in charge" of an instance variable, while others just look at it. The initial value for an instance variable comes from the first component flavor that specifies one. To keep instance variables distinct, use the same mechanism as to keep any other type of symbols distinct: packages. Method combination is the heart of the flavor system. When a flavor is defined, a single function, called a combined method , is constructed for each message supported by the flavor. This function is constructed out of all the methods for that message from all the components of the flavor. There are many different ways that methods can be combined; these can be selected by the user when a flavor is defined. The user can also create new forms of combination. The default way to combine methods is the daemon paradigm. Methods are classified into two kinds, primary and daemon . The idea is that one flavor is "in charge" of the main business of handling a message, while other flavors just want to keep informed, or just want to do the part of the operation associated with their own area of responsibility. The method-combination process selects one primary method for a message; it comes from the first component flavor that has one. Any primary methods belonging to later component flavors are ignored. Daemon methods come in two kinds, before and after . The combined method consists of all the before daemons, then the primary method, then all the after daemons. The returned values from the combined method are the values returned by the primary method; any values from the daemons are ignored. Before-daemons are called in the order that flavors are combined, while after-daemons are called in the reverse order. Note that if you have no daemons, this reduces to the form of inheritance traditional in message-passing systems. Other ways of combining methods will be detailed later (see LINK:(method-combination)). [Much work still required here.] [This paragraph is important and should be expanded. It is what the idea of "conventions" is all about.] The idea of the external contract for an operation and the internal(?) details of the different flavors of that operation. Or, the idea of the external contract for objects conforming to a certain general type, and the specific details of particular flavors of objects.
21.2
defflavor Macro
A flavor is defined by a form
(defflavor name  (var1  var2 ...) (flav1  flav2 ...)
	opt1  opt2 ...)
name is a symbol which serves to name this flavor. It will be returned by typep of an instance of this flavor, and will get an si:flavor property of the internal data-structure containing the details of the flavor. (typep obj flavor-name) is t if obj is an instance of a flavor, one of whose components is flavor-name . var1 , var2 , etc. are the names of the instance-variables containing the local state for this flavor. A list of the name of an instance-variable and a default initialization form is also acceptable; the initialization form is only evaluated if no other initial value for the variable is obtained. If no initialization is specified, the variable remains unbound. flav1 , flav2 , etc. are the names of the component flavors out of which this flavor is built. The features of those flavors are inherited as described above. opt1 , opt2 , etc. are options; each option may be either a keyword symbol or a list of a keyword symbol and arguments. The options to defflavor are described on LINK:(defflavor-options).
*all-flavor-names* Variable
This is a list of the names of all the flavors that have ever been defflavor 'ed.
defmethod Macro
A method, that is, a function to handle a particular message sent to an instance of a particular flavor, is defined by a form such as
(defmethod (flavor-name  message ) lambda-list 
  form1  form2 ...)
message is either just a keyword symbol which names the message to be handled, or two keyword symbols, the first of which is the method type and the second of which is the message name. The meaning of the method type depends on what kind of method-combination is declared for this message. For instance, for daemons :before and :after are allowed. See LINK:(method-combination). lambda-list describes the arguments and "aux variables" of the function; the first argument which is the message keyword is automatically handled and not mentioned here. Note that methods may not have &quote arguments, that is they must be functions, not special forms. form1 , form2 , etc. are the function body. The variant form
(defmethod (flavor-name  message ) function )
where function is a symbol, says that flavor-name 's method for message is function , a symbol which names a function. That function must take appropriate arguments; the first argument is the message keyword. Note that defmethod is also used for defining class methods, in the case where flavor-name is the name of a class rather than of a flavor. See LINK:(class).
self Variable
When a message is sent to an object, the variable self is automatically bound to that object, for the benefit of methods which want to manipulate the object itself (as opposed to its instance variables).
declare-flavor-instance-variables Macro
Sometimes you will write a function which is not itself a method, but which is to be called by methods and wants to be able to access the instance variables of the object self . The form
(declare-flavor-instance-variables (flavor-name )
  function-definition )
surrounds the function-definition with a declaration of the instance variables for the specified flavor, which will make them accessible by name. Currently this works by declaring them as special variables, but this implementation may be changed in the future. Note that it is only legal to call a function defined this way while executing inside a method for an object of the specified flavor, or of some flavor built upon it.
recompile-flavor flavor-name &optional single-message (use-old-combined-methods t) (do-dependents t)
Updates the internal data of the flavor and any flavors that depend on it. If single-message is supplied non-nil , only the methods for that message are changed. The system does this when you define a new method that did not previously exist. If use-old-combined-methods is nil , automatically-generated functions to call multiple methods or to contain code generated by wrappers will be regenerated. Normally these are only regenerated if the set of methods they are based on has changed. If you change a wrapper, you must do recompile-flavor with third argument nil in order to make the new wrapper take effect. If do-dependents is nil , only the specific flavor you specified will be recompiled. Normally it and all flavors that depend on it will be recompiled. recompile-flavor only affects flavors that have already been compiled. Typically this means it affects flavors that have been instantiated, but does not bother with mixins.
compile-flavor-methods Macro
The form (compile-flavor-methods flavor-name-1 flavor-name-2...) , placed in a file to be compiled, will cause the compiler to include the automatically generated methods for the named flavors in the resulting qfasl file, provided all of the necessary flavor definitions have been made. Use of compile-flavor-methods for all flavors that are going to be instantiated is recommended to eliminate the need to call the compiler at run time (the compiler will still be called if incompatible changes have been made, such as addition or deletion of methods that must be called by a combined method).
defwrapper Macro
Sometimes the way the flavor system combines the methods of different flavors (the daemon system) is not powerful enough. In that case defwrapper can be used to define a macro which expands into code which is wrapped around the invocation of the methods. This is best explained by an example; suppose you needed a lock locked during the processing of the :foo message, which takes the arguments arg1 and arg2 , and you have a lock-frobboz special-form which knows how to lock the lock (presumably it generates an unwind-protect ).
(defwrapper (flavor  :foo) ((arg1 arg2) . body)
  `(lock-frobboz (self ,arg1)
     ,@body))
The use of the body macro-argument prevents the defwrapper 'ed macro from knowing the exact implementation and allows several defwrapper 's from different flavors to be combined properly. If you change a wrapper, the change may not take effect automatically. You must use recompile-flavor with a third argument of nil to force the effect to propagate into the compiled code which the system generates to implement the flavor. The reason for this is that the flavor system cannot reliably tell the difference between reloading a file containing a wrapper and really redefining the wrapper to be different, and propagating a change to a wrapper is expensive. [This may be fixed in the future.]
funcall-self message arguments...
When self is an instance or an entity, (funcall-self args...) has the same effect as (funcall self args...) except that it is a little faster since it doesn't bother to think about re-binding the instance variables. If self is not an instance nor an entity, funcall-self and funcall do the same thing.
lexpr-funcall-self message arguments... list-of-arguments
When self is an instance or an entity, (lexpr-funcall-self args...) has the same effect as (lexpr-funcall self args...) except that it is a little faster since it doesn't bother to think about re-binding the instance variables. If self is not an instance nor an entity, lexpr-funcall-self and lexpr-funcall do the same thing.
21.3
There are quite a few options to defflavor . They are all described here, although some are for very specialized purposes and not of interest to most users.
:gettable-instance-variables
Enables automatic generation of methods for getting the values of instance variables. The message name is the name of the variable, in the keyword package (i.e. put a colon in front of it.) If this option is given with arguments, only those instance variables get methods; if the keyword is given by itself all the instance variables listed in this defflavor get methods.
:settable-instance-variables
Enables automatic generation of methods for setting the values of instance variables. The message name is ":set- " followed by the name of the variable. If this option is given with arguments, only those instance variables get methods; if the keyword is given by itself all the instance variables listed in this defflavor get methods. All settable instance variables are also automatically made gettable and initable.
:initable-instance-variables
The instance variables listed as arguments, or all instance variables listed in this defflavor if the keyword is given alone, are made initable . This means that they can be initialized through use of a keyword (colon the name of the variable) in the initialization property-list.
:init-keywordsThe arguments are declared to be keywords in the initialization property-list which are processed by this flavor's :init methods. This is just used by error-checking which looks for entries (presumably misspelled) in the initialization property-list which are not handled by any component flavor of the object being created, neither as initable-instance-variables nor as init-keywords.
:default-init-plist
The arguments are alternating keywords and values, like a property-list. When the flavor is instantiated, if the init-plist does not contain one of these keywords, that keyword and corresponding value are put in. This allows one component flavor to default an option to another component flavor. The values are only evaluated if they are used.
:required-instance-variables
Declares that any flavor incorporating this one which is instantiated into an object will contain the instance variables given as arguments. The difference between listing instance variables here and listing them at the front of the defflavor is that the latter declares that this flavor "owns" those variables and will take care of initializing them, while the former declares that this flavor depends on those variables but that some other flavor must be provided to manage them and whatever features are implied by them. Required instance variables may be freely accessed by methods just like normal instance variables.
:required-methods
The arguments are names of messages which any flavor incorporating this one must handle. An error occurs if there is an attempt to instantiate such a flavor and it is lacking a method for one of these messages. Typically this option appears in the defflavor for a base flavor.
:included-flavors
The arguments are names of flavors to be included in this flavor. The difference between declaring flavors here and declaring them at the top of the defflavor is that when component flavors are combined, all the included flavors come after all the regular flavors. Thus included flavors act like defaults.
:no-vanilla-flavor
Unless this option is specified, si:vanilla-flavor is included (in the sense of the :included-flavors option). vanilla-flavor provides some default methods for the :print , :describe , :which-operations , :eval-inside-yourself , and :funcall-inside-yourself messages.
:default-handler
The argument is the name of a function which is to be called when a message is received for which there is no method. If this option is not specified on any component flavor, it defaults to a function which will signal an error.
:ordered-instance-variables
The arguments are names of instance variables which must appear first (and in this order) in all instances of this flavor, or any flavor depending on this flavor. This is used for instance variables which are specially known about by microcode, and in connection with the :outside-accessible-instance-variables option. If the keyword is given alone, the arguments default to the list of instance variables given at the top of this defflavor .
:outside-accessible-instance-variables
The arguments are instance variables which are to be accessible from "outside" of this object, that is from functions other than methods. A macro (actually a defsubst ) is defined which takes an object of this flavor as an argument and returns the value of the instance variable; setf may be used to set the value of the instance variable. The name of the macro is the name of the flavor concatenated with a hyphen and the name of the instance variable. These macros are similar to the accessor macros created by defstruct (see this link.) If the instance variable is declared to be ordered, the macro will know its relative position in the instance and generate very quick code to access it; otherwise the position has to be computed at run-time, which takes substantially longer but doesn't build the details of the structure of the instance into compiled code.
:select-method-order
This is purely an efficiency hack. The arguments are names of messages which are frequently used or for which speed is important. Their methods are moved to the front of the method table so that they are accessed more quickly.
:method-combination
Declares the way that methods from different flavors will be combined. Each "argument" to this option is a list (type order message1 message2...) . Message1 , message2 , etc. are names of messages whose methods are to be combined in the declared fashion. Type is a keyword which is a defined type of combination, see LINK:(method-combination). Order is a keyword whose interpretation is up to type ; typically it is either :base-flavor-first or :base-flavor-last .
:documentationThe list of arguments to this option is remembered on the flavor's property list as the :documentation property. The (loose) standard for what can be in this list is as follows; this may be extended in the future. A string is documentation on what the flavor is for; this may consist of a brief overview in the first line, then several paragraphs of detailed documentation. A symbol is one of the following keywords:
:mixinA flavor that you may want to mix with others to provide a useful feature.
:essential-mixin
A flavor that must be mixed in to all flavors of its class, or inappropriate behavior will ensue.
:lowlevel-mixin
A mixin used only to build a mixin.
:combinationA combination of flavors for a specific purpose.
:special-purpose
A flavor used for some internal or hackish purpose, which you aren't likely to want yourself.
21.4
instantiate-flavor flavor-name init-plist &optional send-init-message-p return-unhandled-keywords area
This function creates and returns an instance of the specified flavor. init-plist is a disembodied property list, such as a locative to a cell containing a list of alternating keywords and values, which controls how the instance is initialized, as explained below. This property list may get modified; beware! First, if the flavor's method-table and other internal information have not been computed or are not up to date, they are computed. This may take a substantial amount of time and invoke the compiler, but will only happen once for a particular flavor no matter how many instances you make, unless you change something. Next, the instance variables are initialized, in several ways. If an instance variable is declared initable, and a keyword with the same spelling as its name appears in init-plist , it is set to the value specified after that keyword. If an instance variable does not get initialized this way, and an initialization form was specified for it in a defflavor , that form is evaluated and the variable is set to the result. The initialization form may not depend on any instance variables nor on self ; it will not be evaluated in the "inside" environment in which methods are called. If an instance variable does not get initialized either of these ways it will be left unbound; presumably an :init method should initialize it. If any keyword appears in the init-plist but is not used to initialize an instance variable and is not declared in an :init-keywords option, it is presumed to be a misspelling. If the return-unhandled-keywords argument is not supplied, such keywords are complained about by signalling an error. But if return-unhandled-keywords is supplied non-nil , a list of such keywords is returned as the second value of instantiate-flavor . If the send-init-message-p argument is supplied and non-nil , an :init message is sent to the newly-created instance, with one argument, the init-plist . get can be used to extract options from this property-list. Each flavor that needs initialization can contribute an :init method. If the area argument is specified, it is the number of an area in which to cons the instance; otherwise it is consed in the default area.
21.5
Unless you specify otherwise (with the :no-vanilla-flavor option to defflavor ), every flavor includes the "vanilla" flavor, which has no instance variables but provides some basic useful methods. Thus nearly every object may be assumed to handle the following messages. .defmessage :print stream prindepth slashify-p 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 prinlevel ), and whether slashification is enabled (prin1 vs princ ). Vanilla-flavor ignores the last two arguments, and prints something like #<flavor-name octal-address> . The flavor-name tells you what type of object it is, and the octal-address allows you to tell different objects. .end_defmessage .defmessage :describe The object should describe itself. The describe function sends this message when it encounters an instance or an entity. Vanilla-flavor outputs the object, the name of its flavor, and the names and values of its instance-variables. .end_defmessage .defmessage :which-operations The object should return a list of the messages it can handle. Vanilla-flavor generates the list once per flavor and remembers it, minimizing consing and compute-time. .end_defmessage .defmessage :eval-inside-yourself form The argument is a form which 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 setq one of these special variables; the instance variable will be modified. This is mainly intended to be used for debugging. .end_defmessage .defmessage :funcall-inside-yourself function &rest args Function is applied to args 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 setq one of these special variables; the instance variable will be modified. This is mainly intended to be used for debugging. .end_defmessage
21.6
The following types of method combination exist; these can be declared with the :method-combination option to defflavor . It is possible to define your own types of method combination; for information on this, see the code. Note that for most types of method combination other than :daemon you must define the order in which the methods are combined, either :base-flavor-first or :base-flavor-last . In this context, base-flavor means the last element of the flavor's fully-expanded list of components.
:daemonThis is the default type of method combination. All the :before methods are called, then the primary (no type) method for the outermost flavor that has one is called, then all the :after methods are called. The value returned is the value of the primary method.
:progn.item1 :and .item1 :or All the methods are called, inside a progn , and , or or special form. No typed methods are allowed.
:listCalls all the methods and returns a list of their returned values. No typed methods are allowed.
:inverse-listApplies each method to one element of the list given as the sole argument to the message. No typed methods are allowed. Returns no particular value. If the result of a :list -combined message is sent back with an :inverse-list -combined message, with the same ordering and with corresponding method definitions, each component flavor receives the value which came from that flavor.
Which method type keywords are allowed depends on the type of method combination selected. Many of them allow only untyped methods. There are also certain methods types used for internal purposes. Here is a table of all the method types used in the standard system (a user can add more, by defining new forms of method-combination).
(no type) This is the most common type of method.
:before.item1 :after Used by :daemon method-combination.
:defaultA :default method is treated as untyped if there are no other methods for the message among any of the component flavors. Otherwise it is ignored.
:wrapperUsed internally by defwrapper .
:combinedUsed internally for automatically-generated combined methods.
21.7
An object which is an instance of a flavor is implemented using the data type dtp-instance . The representation is a vector whose first element, tagged with dtp-instance-header , points to the internal data for the flavor, and whose remaining elements are value cells containing the values of the instance variables. The internal data for the flavor, known to the microcode as an "instance descriptor", is a defstruct which appears on the si:flavor property of the flavor name. It contains, among other things, the name of the flavor, the size of an instance, the table of methods for handling messages, and information for accessing the instance variables. defflavor creates such a data structure for each flavor, and links them together according to the dependency relationships between flavors. A message is sent to an instance simply by calling it as a function, with the first argument the message keyword. The microcode binds self to the object, binds the instance variables (as special closure variables) to the value cell slots in the instance, and calls a dtp-select-method associated with the flavor. This dtp-select-method associates the message keyword to the actual function to be called. If there is only one method, this is that method, otherwise it is an automatically-generated function which calls the appropriate methods in the right order. If there are wrappers, they are incorporated into this automatically-generated function. There is presently an implementation restriction that when using daemons, the primary method may return at most three values if there are any :after daemons. This is because the combined method needs a place to remember the values while it calls the daemons. This will be fixed some day. The function-specifier syntax (:method flavor-name optional-method-type message-name) is understood by fdefine and related functions, for both flavor methods and class methods.