Next column


The Theory of Classification

Part 15: Mixins and the Superclass Interface

Anthony J H Simons, Department of Computer Science, University of Sheffield, U.K.

space COLUMN

PDF Icon
PDF Version

1 INTRODUCTION

This is the fifteenth article in a regular series on object-oriented type theory for non-specialists. Earlier articles have built up -calculus models of objects [1], classes [2], inheritance [3, 4] and generic template types [5]. These features are common to a number of popular object-oriented languages, such as C++, Eiffel and Java (which now has templates in the latest version). In this article, we look at a less well known, but once popular construct in object-oriented languages called a mixin.

Mixins were first proposed in the language Flavors [7]. A mixin is best described as a freestanding component extension, something that is intended to be added onto another class using the inheritance mechanism. A mixin can be combined with many different base classes, to yield different extended classes which contain the combined base and mixin features. Some mixins provide orthogonal functionality that can be added to any class. Other mixins expect the class with which they are combined to provide certain operations, because the mixin’s own methods depend on them. In other words, a mixin has a superclass interface, describing the kind of class from which it expects to inherit. By examining mixins formally, we can learn more about the type constraints on inheritance.

2 FLAVORS AND MIXINS

Flavors [6, 7] was an important early object-oriented language, developed at MIT in the late 1970s. It was the first to introduce multiple inheritance, the idea that a child class may have more than one parent class and combine all the inherited features in some principled way. As legend has it, this idea was inspired by the presence of several famous ice-cream parlours in the vicinity of MIT. When visiting these emporia, you could choose your basic vanilla ice-cream and then mix in one of several other flavours 1, such as pistachio or strawberry. By analogy, the root class in the new language was called "Vanilla Flavor" and other classes were extensions of this. The idea of a "mixin" was inspired by the extra sauces and toppings you could add to your ice-cream. A mixin is not itself a whole class, but rather a package of optional features that you can choose to add to a class. It is "mixed in" in the sense that, through the mechanism of inheritance, the mixin’s features may become interwoven with the features of the class with which it is combined. Mixins were the first attempt to provide flexible solutions to some of the same problems that are currently addressed using aspects in aspect-oriented programming, which are woven together in a similar way.

A simple example of a mixin might be an extension that adds a coordinate position to any other object. The classes in a library may exist without reference to any coordinate position, for example, a Truck class might describe the intrinsic properties of trucks, and might be just one of many Vehicle subclasses. Then, for a given simulation application, it is desired that some of these classes be locatable within a coordinate system. In Flavors you could create the extended types quickly by combining the canonical types with a Locatable mixin, to yield locatable versions of each class:

(defflavor simulation-truck () (truck locatable-mixin))

The syntax of Flavors may not be familiar to many readers. The language was built on top of Lisp. To construct a class, you called the Lisp function defflavor and provided it with a list of attributes (methods were defined separately). If this class inherited from other classes, they were supplied in a second list. The syntax looked something like:

(defflavor class-name (var-1, var-2, … var-n)
(super-1, super-2, … super-n))

where the various super classes are the names of other classes to be "mixed in" with the new class. There was no real syntactic distinction between a class and a mixin, merely a naming convention, whereby mixin names always ended in "-mixin". Folding in a number of mixins required the linearisation of their properties – in fact, this was no different from the general problem of multiple inheritance. In Flavors, the inheritance algorithm combined features from the superclasses in left-to-right order, merging identically-named attributes, provided that all the recursive orderings declared by the superclasses could be preserved [7].

3 TEMPLATES AND ABSTRACT SUBCLASSES

To illustrate the idea of mixins in another way, we may use the template mechanism in C++ to define "abstract subclasses". The Locatable mixin described above might be simulated in C++ as the following "abstract subclass":

Listing 1: C++ definition of an "abstract subclass"

Locatable is defined like a C++ subclass, but inherits from a type parameter Any, which has the effect of delaying the combination of local features with the (as yet unknown) inherited features from some eventual base class. Locatable versions of the various Vehicle subclasses may be constructed on the fly, in the style:

Locatable<Car> car1;
Locatable<Bus> bus1;

at which point the Any parameter is bound to the specific types Car and Bus, respectively. Any C++ class which inherits from a type parameter is an "abstract subclass". The parameter helps to underline how it expects to be combined with some unknown base class.

4 MIXINS VERSUS ABSTRACT SUBCLASSES

The difference between this "abstract subclass" approach and the earlier "mixin" approach is that Flavors defines each mixin component independently, as a freestanding extension. Combining mixins is more like multiple inheritance in C++ (with virtual base classes), in which the programmer derives a new subclass which "mixes" all the components. The abstract subclass approach is more like a wrapper function, which expects to be applied to some base object denoting super, and then combines the additional fields with the base fields yielding the subtype directly.

Bracha and Cook described a mixin as "an abstract subclass" or "a subclass definition that may be applied to different superclasses" [8]. Here, we would prefer a slightly more careful use of the term "mixin", since we want to draw a formal difference between a subclass and a mixin, which we can illustrate using the model of inheritance from earlier articles [3, 4]. Recall that inheritance is modelled as the combination of records using the union with override operator:

In this, base is the parent object and derived is the subclass object, constructed by combining base with a record of extra fields. It is clear that derived is the result of the combination, a whole subclass object, rather than just an extension. On the other hand, the extra record of additional fields is exactly what we mean by a mixin. The notion of an abstract subclass should therefore be modelled as a function:

and this illustrates the difference : the abstract subclass is a function which includes the inheritance operator, whereas the mixin is simply a record of extra fields.

5 MIXINS AS EXTENSION TYPES

Most object-oriented languages don’t encourage the specification of mixins in isolation, but instead, records of extra fields are typically declared within the scope of a complete subclass definition (like the example in section 3 above). The reasons for this have to do with self-reference and the typing of inheritance. When a conventional object subtype is defined (in the style of Java or C++), references to the self-type in the extension are equivalent to the eventual subtype [2], rather than the type of the extension. We can illustrate this with a Point subtype that extends an Object base type:

Note that the extension record has an equal method accepting the self-type , which after unrolling the recursion, is equivalent to Point. This is because is recursively bound outside the union of base fields and extra fields, to the result of the union. The self-type in the extension record is therefore not independent, but refers to the subtype.

If Java or C++ allowed the programmer to declare mixins as freestanding records of extra fields to be added to any class, these would have an independent self-type of their own:

Note how the self-type of the PointMixin is bound independently, to refer recursively to the PointMixin type. After combination with the Object type, this yields a Point type in which self-type reference is entirely schizophrenic [3]: the inherited self-type is Object, and the extension self-type is PointMixin, but nowhere is the self-type equivalent to the Point type! So, for this reason, languages based on subtyping would have trouble dealing with self-reference if they wished to admit freestanding types as mixins.

6 MIXINS AS EXTENSION GENERATORS

Flavors is a language, like Smalltalk and Eiffel, in which self is rebound during inheritance to refer to the subclass instance. Accordingly, the self-type evolves during inheritance to refer to the subclass’s type. In earlier articles, we found that type generators could be used to describe this kind of flexibility in the self-type [2, 3]. The type of a mixin can be expressed using a type generator, instead of a fixed type:

In this, the self-type is a parameter introduced by , and is not yet bound to any specific type. Given a similar generator for the Object type, we can construct a generator for the Point type, by unifying the self-types of the base and mixin generators in the combination:

Here, the subclass generator GenPoint introduces a new self-type ? and propagates this into both the base generator GenObject and the extension generator GenPointMixin (by applying them to the new type), so creating two record types which refer to the same self-type ?. After the union of fields, ? refers homogeneously to the self-type. After fixing the recursion, ? is bound to the desired Point type.

7 BOUND AND FREE MIXINS

We characterise mixins as either bound or free, to denote whether or not they depend on their superclass. The GenPointMixin type generator above was defined as though it were the type of a free mixin, capable of being combined with any other object, since its methods were assumed not to interact with any superclass behaviour. To examine this in more detail, we can build an object generator to represent the implementation of the mixin [4]:

The body of the implementation has no dependency on any super-object, illustrating the independence of the mixin. One possible weakness in this design is that the equal method can only compare the local x and y values. If this were "mixed in" with some other base object with an equal method, the inherited method would be overridden by the mixin’s version.

To illustrate this, we introduce the object generator genSquare, representing a geometric square with its own side and equal methods:

We may seek to combine the genSquare and freePointMixin generators by inheritance; the resulting generator genLocSquare represents a locatable square:

Although two versions of equal are present before record combination, the operator prefers fields from the right-hand side, replacing any identically-named fields on the left. The inherited version of equal is therefore overridden and the version of equal obtained in the result is insufficient, because we can no longer compare the sides of squares.

Since equal is a common method and is likely to exist in most classes, we would prefer our mixin to adapt the equal method of its base class, rather than replace it wholesale. In an earlier article [9] we showed how inherited methods could be adapted by method combination, in which a redefined version of the method calls the original version through the super variable. To make inherited methods available to a mixin, we have to supply it with a variable standing for the super-object. The following is an object generator for a bound mixin, whose implementation depends on both self and super variables. The super variable will be bound later to a superclass instance:

The dependency on the super-object is evident in the revised body of the equal method, which calls super.equal(p)before comparing the respective x and y values. Note how a bound mixin generator must always have an extra argument, super, to bind to the eventual base object.

Once again, we combine the genSquare generator with the boundPointMixin generator to obtain a generator for the locatable square, genLocSquare. Note in passing how self is reintroduced outside of the record combination, and how both generators accept this new value of self, to ensure uniform self-reference. In addition, the boundPointMixin receives an actual argument genSquare(self), representing the value of super; in fact this same expression denotes the base object on the left-hand side of record combination:

After simplification, the result has exactly the desired implementation of a generator for a locatable square object. The super.equal(p) expression is expanded during this simplification to yield the body of the inherited method, which is combined using logical and with the further parts of the redefined equal method.

8 THE SUPERCLASS INTERFACE

We now want to add types to the bound mixin implementation described above. In this, we shall need to provide polymorphic types for self and for super. The typing of self is relatively straightforward, but the typing of super is shown below to be much more difficult. It is particularly desirable to try to establish the type of super, since this captures exactly the superclass interface of a mixin, describing the type of object with which it expects to be combined. However, the type of super is made more complex by the fact that the eventual self- and super-types must stand in a subtyping relationship. So, although these two types would appear on the surface to be independent, they are in fact related. Other treatments of typed mixins [11] have expressed this by introducing the super-type first, then a dependent self-type. Our novel approach reverses the order of dependency, introducing the self-type first, then a dependent super-type.

Previously, we showed how the type of self in an object generator could be given by an F-bound constructed from the corresponding type generator [10]. All combinations with the boundPointMixin form a class with at least the x, y and equal methods in their interface. The type generator GenPointMixin (from section 6) already describes this interface:

such that we may give a polymorphic type for self ranging over all those types with (at least) these methods:

What is unusual about this is that it does not depend on the type of super in any way. We would normally have expected to use a type generator of two arguments, and , reflecting the two-argument structure of the object generator. The reason why we can ignore the super-type is because it never appears in the public interface of the class – it is irrelevant! This allows us to introduce the self-type independently, using the simpler type generator.

The polymorphic type of super may now be expressed as a range of types within certain bounds. The lower bound is the type of self (because self : must eventually stand in a subtyping relationship with super : ), which gives rise to the lower bound condition:

The upper bound is expressed in terms of a minimum type, determined by examining the methods that are invoked on the super variable, then constructing an interface which supports at least these methods. We can only do this by internal inspection of the object generator implementation. In the example above, the boundPointMixin expects the super object to have at least an equal method. Accordingly, we may construct a type-generator representing the interface of all objects possessing (just) the equal method:

and from this create the upper bound condition:

What is different here is that the constraint is not expressed as: <: GenEqual[], but rather in terms of the self-type . This is because, at the time the super-type is bound, all generator-types will be adapted to the current self-type ?. Combining the lower and upper bound constraints on the super-type yields the range:

This, finally, is the type of the superclass interface! It is quite complicated, but intuitively expresses the idea that the eventual type of super is a supertype of self and a subtype of the interface providing the equal method.

9 TYPED MIXIN COMBINATION

We may now observe the interplay of types when we combine a typed version of the boundPointMixin with a typed version of the canonical square. First, we attach types to the mixin, as determined above:

The typed version of the canonical square is given in the usual way by:

The typed locatable square is to be derived by adding the point mixin to the canonical square. First, we establish the resulting type generator, GenLocSquare:

The typed locatable square is then given by the "mixed in" combination:

The result is a typed object generator for a locatable square, exactly as desired.

We now want to check whether the type constraints of the typed mixin generator were properly observed. In the combination, the mixin generator was called with:

  • the new value of self, having the reintroduced self-type: ]
  • the value of super, genSquare(self), having the adapted type: GenSquare[]

The self-type was expected to satisfy: (]). This holds by the rule of classification [3]. Since the two generators stand in a pointwise subtyping relationship:

then if ].

The super-type GenSquare[] was expected to satisfy: <: GenEqual[]). We can check this by the substitution of {GenSquare[} to see if both the lower and upper bound conditions hold. Firstly, we examine the lower bound:

This holds, because the two generators stand in a pointwise subtyping relationship:

therefore if ] then it follows that ]. Secondly, we examine the upper bound:

Again, we can show that the two generators stand in a pointwise subtyping relationship for all possible types :

therefore they still stand in this relationship for some subset of types . So, we have demonstrated that mixins can be typed and applied to base classes which properly satisfy the superclass interface.

10 CONCLUSION

We started this article by presenting the concept of a mixin. A mixin is a freestanding record of extra fields, intended to be combined with any other object. In some sense, mixins are the primitive building blocks in languages with inheritance-like mechanisms. Bracha and Cook revived interest in mixins when they demonstrated how the models of inheritance in languages as diverse as Smalltalk, Beta and CLOS could all be mapped onto a simpler model based on the composition of mixins [8]. However, the typing they gave was based on simple first-order types, resulting in fragmented self-types after record combination. This is why mixins don’t receive so much attention in Java and C++, and one reason why multiple inheritance is less useful in languages like C++ which are based on simple subtyping.

Mixins are much more interesting in those languages which modify self-reference and the self-type during inheritance. They then have some of the power of aspects in aspect-oriented programming, since they can weave in extra attributes and methods and even adapt the course of an inherited method through method combination. However, the formal characterisation of mixins was previously thought difficult. In particular, it was thought that the type of the superclass interface was impossible to express without going to higher order logics. This is because super apparently ranges over a set of classes, so quantification should have to range over sets of generators (type functions), rather than just over sets of simple types. In earlier work by Harris and others [11], the higher-order type of super was introduced first, and then the type of self, which depended on the type of super. Here, we showed how it is possible to provide a second-order type for super, by reversing the order in which the self-type and super-type are introduced. This provided an elegant typing for mixins, which was checked using an example of typed mixin combination.


Footnotes

1 With apologies to US readers, I and my spell-checker prefer British spelling, but proper names like “Flavors” are allowed to remain in their proprietary form!

 

REFERENCES

[1] A J H Simons: "The theory of classification, part 3: Object encodings and recursion", in Journal of Object Technology, vol. 1, no. 4, September-October 2002, pp. 49-57. http://www.jot.fm/issues/issue_2002_09/column4

[2] A J H Simons: "The theory of classification, part 7: A class is a type family", in Journal of Object Technology, vol. 2, no. 3, May-June 2003, pp. 13-22. http://www.jot.fm/issues/issue_2003_05/column2

[3] A J H Simons: "The theory of classification, part 8: Classification and inheritance", in Journal of Object Technology, vol. 2, no. 4, July-August 2003, pp. 55-64. http://www.jot.fm/issues/issue_2003_07/column4

[4] A J H Simons: :The theory of classification, part 9: Inheritance and self-reference", in Journal of Object Technology, vol. 2, no. 6, November-December 2003, pp. 25-34. http://www.jot.fm/issues/issue_2003_11/column2

[5] A J H Simons: "The theory of classification, part 13: Template Classes and Genericity", in Journal of Object Technology, vol. 3, no. 7, July-August 2004, pp. 15-25. http://www.jot.fm/issues/issue_2004_07/column2

[6] H Cannon, Flavors, Technical Report (Cambridge: MIT AI Laboratory, 1980).

[7] D A Moon, "Object-oriented programming with Flavors", Proc. 1st ACM Conf. Object-Oriented Prog. Sys., Lang. and Appl., pub. ACM Sigplan Notices, 21(11), (ACM Sigplan, 1986), 1-6.

[8] G Bracha and W Cook, "Mixin-based inheritance", Proc. 5th ACM Conf. Object-Oriented Prog. Sys., Lang. and Appl. and Proc. 4th European Conf. Object-Oriented Prog., pub. ACM Sigplan Notices, 25(10) (ACM Sigplan, 1990), 303-311.

[9] A J H Simons: "The theory of classification, part 10: Method combination and super-reference", in Journal of Object Technology, vol. 3, no. 1, January-February 2004, pp. 43-53. http://www.jot.fm/issues/issue_2004_01/column4

[10] A J H Simons: "The theory of classification, part 11: Adding class types to object implementations", in Journal of Object Technology, vol. 3, no. 3, March-April 2004, pp. 7-19. http://www.jot.fm/issues/issue_2004_03/column1

[11] W Harris, Typed Object-Oriented Programming: ABEL Project Posthumous Report, Hewlett-Packard Laboratories (1991).

 

About the author



space Anthony Simons is a Senior Lecturer and Director of Teaching in the Department of Computer Science, University of Sheffield, where he leads object-oriented research in verification and testing, type theory and language design, development methods and precise notations. He can be reached at a.simons@dcs.shef.ac.uk.


Cite this column as follows: Anthony J.H. Simons: “The Theory of Classification, Part 15: Mixins and the Superclass Interface”, in Journal of Object Technology, vol. 3, no. 10, November-December 2004, pp. 7-18. http://www.jot.fm/issues/issue_2004_11/column1


 

Next column