HAL Id: hal-01544118
https://hal.archives-ouvertes.fr/hal-01544118
Submitted on 26 Jun 2017
HAL is a multi-disciplinary open access
archive for the deposit and dissemination of
sci-entific research documents, whether they are
pub-lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.
L’archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d’enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.
Binary Methods Programming: the Clos Perspective
Didier Verna
To cite this version:
Binary Methods Programming: the Clos Perspective
Didier Verna
(Epita Research and Development Laboratory, Paris, France [email protected])
Abstract: Implementing binary methods in traditional object-oriented languages is difficult: numerous problems arise regarding the relationship between types and classes in the context of inheritance, or the need for privileged access to the internal repre-sentation of objects. Most of these problems occur in the context of statically typed languages that lack multi-methods (polymorphism on multiple arguments). The pur-pose of this paper is twofold: first, we show why some of these problems are either non-issues, or easily solved in Common Lisp. Then, we demonstrate how the Com-mon Lisp Object System (Clos) allows us not only to implement binary methods in a straightforward way, but also to support the concept directly, and even enforce it at different levels (usage and implementation).
Key Words: Binary methods, Common Lisp, object orientation, meta-programming Category: D.1.5, D.3.3
1
Introduction
Binary operations work on two arguments of the same type. Common examples include arithmetic operations (=, +, − etc.) and ordering relations (=, <, > etc.). In the context of object-oriented programming, it is often desirable to implement binary operations as methods applied on two objects of the same class in order to benefit from polymorphism. Such methods are hence called “binary methods”.
Implementing binary methods in many traditional object-oriented languages is a difficult task: the relationship between types and classes in the context of inheritance and the need for privileged access to the internal representation of objects are the two most prominent problems. In this paper, we approach the concept of binary method from the perspective of Common Lisp.
The paper is composed of two main parts. In section 2, we show how two problems mentioned above are either non-issues, or easily solved. In section 3, we show how to support the concept of binary methods directly into the language, and demonstrate how to ensure not only a correct usage of it, but also a correct implementation of it.
2
Binary methods non-issues
types and classes within an inheritance scheme, and the need for privileged access to the internal representation of objects. We show why the former is a non-issue in Common Lisp, and how the latter can be solved. In order to illustrate our matter, we take the same examples as used by [Bruce et al., 1995], and provide excerpts from a sample implementation in C++ for comparison.
2.1 Types, classes, inheritance
Consider a Point class representing 2D points from an image, equipped with an equality operation. Consider further a ColorPoint class representing a Point associated with a color. A natural implementation would be to inherit from the Point class, as shown in listing 1 (C++ version, details omitted).
c l a s s P o i n t c l a s s C o l o r P o i n t : p u b l i c P o i n t
{ {
i n t x , y ; s t d : : s t r i n g c o l o r ;
bool e q u a l ( P o i n t& p ) bool e q u a l ( C o l o r P o i n t& cp ) { return x == p . x && y == p . y ; } {
} ; return c o l o r == cp . c o l o r && P o i n t : : e q u a l ( cp ) ; }
} ;
Listing 1: Excerpt from the Point class
However, this implementation does not behave as expected because what we have done in the ColorPoint class is simply overload the equal method: ColorPoint objects manipulated as Point ones will only see the definition for equal from the base class, as demonstrated in listing 2.
bool f o o ( P o i n t& p1 , P o i n t& p2 ) C o l o r P o i n t p1 ( 1 , 2 , ” r e d ” ) ; { C o l o r P o i n t p2 ( 1 , 2 , ” b l u e ” ) ;
// P o i n t : : e q u a l i s c a l l e d
return p1 . e q u a l ( p2 ) ; f o o ( p1 , p2 ) ; // => t r u e . Wrong ! }
Listing 2: Method overloading
method to change type as in figure 1, because this would not statically type check.
2.1.1 The static type safety problem
By definition of inheritance, a ColorPoint is a Point, so it should be possible to use a ColorPoint where a Point is expected. Consider the situation described in listing 3. The function foo expects two Point arguments, but actually gets a ColorPoint as the first one. Assuming that the equal method from the exact class is called (hence ColorPoint::equal), we see that this method could try to access the color field in a Point, which does not exist. Therefore, if we want to preserve static type safety, this code should not compile.
bool f o o ( P o i n t& cp , P o i n t& p ) C o l o r P o i n t cp ( 1 , 2 , ” r e d ” ) ;
{ P o i n t p ( 1 , 2 ) ;
return cp . e q u a l ( p ) ;
} f o o ( cp , p ) ; // => ???
Listing 3: The static type safety problem
In order to prevent this situation from happening, we see that the ColorPoint::equal method should not expect to get anything more specific than a Point object. More precisely, maintaining static type safety in a context of inheritance implies that polymorphic methods must follow a contravariance [Castagna, 1995] rule on their arguments: a derived method in a subclass can be prototyped as accepting arguments of the same class or of a superclass of the original arguments only.
2.1.2 A non-issue in Common Lisp
In languages such as C++, methods belong to classes and the polymorphic dispatch depends only on one parameter: the class of the object through which the method is called. The Common Lisp Object System (Clos [Keene, 1989]), on the other hand, differs in two important ways.
1. Firstly, methods do not belong to classes: a polymorphic call appears in the code like an ordinary function call. Functions the behavior of which are provided by such methods are called generic functions.
( d e f c l a s s p o i n t ( ) ( d e f c l a s s c o l o r − p o i n t ( p o i n t ) ( ( x : r e a d e r point−x ) ( ( c o l o r : r e a d e r p o i n t − c o l o r ) ) )
( y : r e a d e r point−y ) ) )
( defmethod p o i n t= ( defmethod p o i n t=
( ( a p o i n t ) ( b p o i n t ) ) ( ( a c o l o r − p o i n t ) ( b c o l o r − p o i n t ) ) ( and (= ( point−x a ) ( point−x b ) ) ( and ( s t r i n g= ( p o i n t − c o l o r a )
(= ( point−y a ) ( point−y b ) ) ) ) ( p o i n t − c o l o r b ) ) ( c a l l− n e x t − m e t h o d ) ) )
Listing 4: The Point hierarchy in Common Lisp
In order to clarify this, listing 4 provides a sample implementation of the Point hierarchy in Common Lisp (details omitted). As you can see, point and color-point classes are defined with only data members (called slots in the Clos jargon). Instead of being class members, two methods on the generic function point= are defined by calls to defmethod. As you can see, a special ar-gument syntax lets you specify the expected class of each: we provide a method for comparing two point objects, and one for comparing two color-point ones. Testing for equality between two points is now simply a matter of calling the generic function as follows:
(point= p1 p2)
According to the exact classes of both of the objects, the correct method is used. The case where the generic function would be called with two arguments of different classes (for example, point and color-point) will be addressed in section 3.2.
2.2 Privileged access to objects internals
The second problem exposed by [Bruce et al., 1995] involves more complex sit-uations in which the need for accessing the objects internals (normally hidden from public view) is required.
Consider a type IntergerSet, representing sets of integers, with an interface providing methods such as the following (their purpose should be obvious): add (i: Integer): Unit
member (i: Integer): Boolean
and also a binary method like the one below:
superSet (a: IntegerSet, b: IntegerSet): Boolean
bitstring or whatever else. While implementing add and member is not an issue at all, the binary method is problematic. Indeed, this method needs to access the individual elements of the sets. It is possible to enrich the above interface with a method returning the sets elements in a single format (for instance, a list), but the concern expressed by [Bruce et al., 1995] is that it might be preferable to work directly on the internal representation for efficiency reasons. The conclusion they draw from this example is twofold:
1. a mechanism is needed for constraining both arguments of the binary method to be not only of the same type, but also of the same implementation, 2. another mechanism is also needed to allow access to this internal
represen-tation while keeping it hidden from general public view.
2.2.1 Types vs. implementation
It would be slightly abusive to claim that point 1 above is a “non-issue” in Common Lisp because the question does not arise exactly in the same terms. When considering constraining both type and implementation to be the same, the authors are silently assuming that there is (or should be) a clear distinction between them. As a matter of fact, Clos does not explicitly provide any such distinction.
In dynamic languages such as Common Lisp however, we might think of solutions in which this distinction is intentionally blurred. For instance, we can define a single integer-set class equipped with a set slot, and let different instances of this class use different set types (lists, arrays, bitstrings etc.) at run-time. In such a case, the super-set function need not be generic anymore (since we have only one class to deal with), but will in turn involve a generic call to effectively compare sets, once their actual type is known.
Also, note that contrary to the first conclusion drawn by [Bruce et al., 1995], the multiple dispatch offered by Common Lisp generic functions will allow us to implement this comparison even for different kinds of sets (however, this cannot be considered a “binary” operation anymore).
2.2.2 Data encapsulation
Back to our original example (the point class), we now roughly describe how one would use the package system to perform implementation hiding. Many important aspects of Common Lisp packages are omitted because our point is not to describe them thoroughly.
( defpackage : p o i n t ( i n − p a c k a g e : p o i n t ) ( : u s e : c l )
( : export : p o i n t ( d e f c l a s s p o i n t ( ) : point−x ( ( x : r e a d e r point−x ) : point−y ) ) ( y : r e a d e r point−y ) ) )
Listing 5: The point class package
The right side of listing 5 shows a definition of the point class, which is no different from the one in listing 4; there is nothing in the class definition to separate interface from implementation. Only the first line of code is new: it merely tells Common Lisp that the current package should be a certain one named point. When Common Lisp encounters, at read-time, a name for a sym-bol which is not found, it automatically creates the corresponding symsym-bol and adds it to the current package. In our case, the effect is to add 5 new symbols into the point package: point, x, y, point-x and point-y. Note that we are only talking about symbols here. Associated variables or functions do not belong to packages.
In order to effectively declare what is “public” and “private” in a package, one has to provide a package definition such as the one depicted on the left side of listing 5. The :use clause specifies that while in the point package, all public symbols from the cl package (the one that provides the Common Lisp standard) are also directly accessible. Consider that if this clause had been missing, we could not have accessed the macro definition associated with the symbol defclass. The :export clause specifies which symbols are public. As you can see, the class name and the accessors are made so, but the slot names remain private.
Now, in order to access the public (exported) symbols of the point pack-age, one has two options. The first one is to use symbol names qualified by the package name, such as point:point-x. The second option is to :use the pack-age, in which case all exported symbols become directly accessible, without any qualification. Hence, the point= method in listing 4 can be used as-is.
could access the slot values in the point class at any place in the code using the symbol names point::x and point::y.
Accessing a package’s private symbols should be considered bad programming style, and used with caution because it effectively breaks modularity. But it is nevertheless easy to do so, and although maybe surprising, is typical of the Lisp philosophy: be very permissive in the language and put more trust on the programmer’s skills.
One important design consideration here is that the package system and the object-oriented layer are completely orthogonal: compare this with languages such as C++ in which information hiding is done by the object-oriented layer itself (public, protected and private members). Also, note that no additional mechanism is needed for privileged access either. One simply uses an additional colon when one really wants to. Again, compare this with languages such as C++ in which an additional machinery is needed (friend functions, methods or classes).
For the record, note that Common Lisp allows for completely hiding symbols (they are said to be uninterned ), but doing that is definitely not the “Lisp way”.
3
Binary methods enforcement
While the previous section demonstrated how straightforward it is to implement binary methods, there is no explicit support for them in the language. In the re-mainder of this paper, we gradually add support for the concept itself, thanks to the expressiveness of Clos and the flexibility of the Clos Meta-Object Protocol (Mop). From now on, we will use the term “binary function” as a shorthand for “binary generic function”.
3.1 Method combinations
When calling point= with two color-point objects, both of the methods we defined are applicable because a color-point object can be seen as a point one. More generally, for each generic function call, several methods might fit the classes of the arguments. These methods are called “applicable methods”.
An interesting feature of Clos is that, contrary to other object-oriented languages where only one method is applied (this is also the default behavior in Clos), it is possible to use all, or some of the applicable methods to form the global execution of the generic function (resulting in what is called an effective method ).
This concept is known as method combination: a way to combine the results of all or some of the applicable methods in order to form the result of the generic function call itself. Clos provides several predefined method combinations, as well as the possibility for the programmer to define his own.
In our example, one particular (predefined, for that matter) method combi-nation is of interest to us: our equality concept is actually defined as the logical and of all local equalities in each class. Indeed, two color-point objects are equal if their color-point-specific parts are equal and if their point-specific parts are also equal.
This can be directly implemented by using the and method combination, as shown in listing 6.
( d e f g e n e r i c p o i n t= ( a b ) ( defmethod p o i n t= and ( : method−combination and ) ) ( ( a p o i n t ) ( b p o i n t ) )
( and (= ( point−x a ) ( point−x b ) ) (= ( point−y a ) ( point−y b ) ) ) ) ( defmethod p o i n t= and
( ( a c o l o r − p o i n t ) ( b c o l o r − p o i n t ) ) ( s t r i n g= ( p o i n t − c o l o r a ) ( p o i n t − c o l o r b ) ) )
Listing 6: The and method combination
As you can see, the call to defgeneric (otherwise optional) specifies the method combination we want to use, and both calls to defmethod are modified accordingly. The advantage of this new scheme is that each method can now con-centrate on the local behavior only: there is no more call to (call-next-method), as the logical and combination is performed automatically. This also has the ad-vantage of preventing possible bugs resulting from an unintentional omission of this very same call.
Note that what we have done here is actually modify the semantics of the dynamic dispatch mechanism. While other object-oriented languages offer one single, hard-wired, dispatch procedure, Clos lets you (re)program it.
3.2 Enforcing a correct usage of binary functions
implementa-tion. Our equality concept requires that only two objects of the same exact class be compared. However, nothing prevents one from using the point= binary function for comparing a color-point with a point for instance. Our current implementation of point= is unsafe because such a comparison is perfectly valid code and the error would go unnoticed. Indeed, since a color-point is a point by definition of inheritance, the first specialization (the one on the point class) is an applicable method, so the comparison will work, but only check for point coordinates.
3.2.1 Introspection in Clos
We can solve this problem by using the introspection capabilities of Clos: it is possible to retrieve the class of a particular object at run-time. Consequently, it is also very simple to check that two objects have the same exact class, an trigger an error otherwise. In listing 7, we show a new implementation of point= making use of the function class-of to retrieve the exact class of an object, in order to perform such a check.
( defmethod p o i n t= and ( ( a p o i n t ) ( b p o i n t ) ) ( a s s e r t ( eq ( c l a s s − o f a ) ( c l a s s − o f b ) ) ) ( and (= ( point−x a ) ( point−x b ) )
(= ( point−y a ) ( point−y b ) ) ) )
Listing 7: Introspection example in Clos
We chose to perform this check only in the least specific method in order to avoid code duplication, because we know that this method will be used for all point objects, including instances of subclasses. One drawback of this approach is that since this method is always called last, it is a bit unsatisfactory to perform the check in the end, after all more specific methods have been applied, possibly for nothing.
3.2.2 Before-methods
initial implementation described in listing 4, listing 8 demonstrates how to prop-erly place the check for class equality. Note the presence of the :before keyword in the method definition.
( defmethod p o i n t= ( ( a p o i n t ) ( b p o i n t ) ) : befor e ( a s s e r t ( eq ( c l a s s − o f a ) ( c l a s s − o f b ) ) ) )
Listing 8: Using before-methods
We want this check to be performed for all point objects, including instances of subclasses, so this method is specialized only on point, and hence applica-ble to the whole potential point hierarchy. But note that even when passing color-point objects to point=, the before-method is executed before the pri-mary ones, so an occasional usage error is signaled as soon as possible. This scheme effectively removes the need to perform the check in the first method itself, which is much cleaner at the conceptual level.
3.2.3 A meta-class for binary functions
There is still something conceptually wrong with the solutions proposed in the previous sections: the fact that it makes no sense to compare objects of differ-ent classes belongs to the concept of binary function itself, not to the point= operation. In other words, if we ever add a new binary function to the point hierarchy, we don’t want do duplicate the code from listings 7 or 8 yet again.
What we really need is to be able to express the concept of binary function directly. A binary function is a generic function with a special, constrained behavior (taking only two arguments of the same class). In other words, it is a specialization of the general concept of generic function. This strongly suggests an object-oriented model, in which binary functions are subclasses of generic functions. This conceptual model happens to be accessible if we delve a bit more into the Clos internals.
Clos itself is written on top of a Meta Object Protocol, simply known as the Clos Mop [Paepcke, 1993, Kiczales et al., 1991], which architects Clos itself in an object-oriented fashion: classes (the result of calling defclass) are Clos (meta-)objects, that is, instances of certain (meta-)classes. Similarly, a user-defined generic function (the result of calling defgeneric) is a Clos object of class standard-generic-function. We are hence able to implement binary functions by subclassing standard generic functions, as shown in listing 9.
( d e f c l a s s b i n a r y − f u n c t i o n ( s t a n d a r d − g e n e r i c − f u n c t i o n ) ( ) ( : m e t a c l a s s f u n c a l l a b l e − s t a n d a r d − c l a s s ) ) ( defmacro d e f b i n a r y ( f u n c t i o n − n a m e l a m b d a − l i s t &r e s t o p t i o n s ) (when ( a s s o c ’ : g e n e r i c − f u n c t i o n − c l a s s o p t i o n s ) ( e r r o r ” : g e n e r i c − f u n c t i o n − c l a s s o p t i o n p r o h i b i t e d ” ) ) ‘ ( d e f g e n e r i c , f u n c t i o n − n a m e , l a m b d a − l i s t ( : g e n e r i c − f u n c t i o n − c l a s s b i n a r y − f u n c t i o n ) , @ o p t i o n s ) )
Listing 9: The binary function class
instances of this class are meant to be called as functions, it is also required to state that the binary function meta-class (the class of the binary-function class meta-object) is a “funcallable” meta-object. This is done through the :metaclass option, which is given funcallable-standard-class and not just standard-class.
Now that we have a proper meta-class for binary functions, we need to make sure that our binary generic functions are instantiated from it. Nor-mally, one specifies the class of newly created generic functions by passing a :generic-function-class argument to defgeneric. If this argument is omit-ted, generic functions are instantiated from the standard-generic-function class. With a few lines of macrology, we make this process easier by providing a defbinary macro that is to be used instead of defgeneric. This macro is designed as a syntactic clone of defgeneric, but we could also think of all sorts of modifications, including enforcing the lambda-list (the generic call prototype) to be of exactly two arguments etc.
3.2.4 Back to introspection
Now that we have an explicit implementation of the binary function concept, let us get back to our original problem: how and when can we check that only points of the same class are compared ?
For each generic function call, we saw that Clos must calculate the sorted list of applicable methods for this particular call. In most cases, this can be fig-ured out from the classes of the arguments to the generic call. The Clos Mop implements this by calling compute-applicable-methods-using-classes (c-a-m-u-c for short).
( defmethod c−a−m−u−c : b e f o r e ( ( b f b i n a r y − f u n c t i o n ) c l a s s e s ) ( a s s e r t ( apply #’eq c l a s s e s ) ) )
Listing 10: Back to introspection
This generic function is interesting to us because, conceptually speaking, before even calculating the applicable methods given the arguments classes, we should make sure that these two classes are the same. This strongly suggests a specialization with a before-method (see section 3.2.2), and this is demonstrated in listing 10. As you can see, this new method only applies to binary functions, thanks to the specialization of its first argument on the binary-function class. The advantage is that the check now belongs to binary function concept itself, and not anymore to each individual function one might want to implement.
3.3 Enforcing a correct implementation of binary functions
In the previous section, we have made sure that binary functions are used as intended, and we have made that part of the their implementation. In this sec-tion, we make sure that binary functions are implemented as intended, and we also make this requirement part of their implementation.
3.3.1 Properly defined methods
Just as it makes no sense to compare points of different classes, it makes even less sense to implement methods to do so. The Clos Mop is expressive enough to make it possible to implement this constraint directly.
When a call to defmethod is issued, Clos must register the new method into the concerned generic function. This is done in the Mop through a call to add-method. It is not an ordinary function, but a generic one, taking two arguments: first, the generic function meta-object involved in the call (in our case, that would be the point= one created by the call to defgeneric), and the newly created method object.
This generic function is interesting to us because, conceptually speaking, before registering the new method, we should make sure that it specializes on two identical classes. This strongly suggests a specialization with a before-method (see section 3.2.2), and this is demonstrated in listing 11.
( defmethod add−method : b e f o r e ( ( b f b i n a r y − f u n c t i o n ) method ) ( a s s e r t ( apply #’eq ( m e t h o d − s p e c i a l i z e r s method ) ) ) )
Listing 11: Binary method definition check
the method’s prototype. In our examples, that would be (point point) or (color-point color-point), so all we have to do is check that the members of this list are actually the same.
3.3.2 Binary completeness
One might realize that our point= concept is not yet completely enforced, if for instance, the programmer forgets to implement the color-point specialization: when comparing to points at the same coordinates but with different colors, only the coordinates would be checked and the test would silently yet mistakenly succeed. It would be an interesting safety measure to ensure that for each defined subclass of the point class, there is also a corresponding specialization of the point= function (we call that binary completeness), and it should be no surprise that the Clos Mop lets you do just that.
Remember that the function c-a-m-u-c is used to sort out the list of ap-plicable methods. Again, this is very interesting to us because the check for binary completeness involves introspection on exactly this list (to see if some methods are missing). What we can do is thus specialize on the primary method this time, retrieve the list in question simply by calling call-next-method, and then do our own work, as depicted in listing 12. The built-in c-a-m-u-c returns two values, the first of which is the list of applicable methods. After we perform our check for completeness (and possibly trigger an error), we simply return the values we got from the default method.
( defmethod c−a−m−u−c ( ( b f b i n a r y − f u n c t i o n ) c l a s s e s ) ( m u l t i p l e − v a l u e − b i n d ( methods ok ) ( c a l l − n e x t − m e t h o d ) (when ok ; ; Check f o r b i n a r y c o m p l e t e n e s s ) ( values methods ok ) ) )
Listing 12: Binary completeness skeleton
would go unnoticed). This is demonstrated in listing 13. The most specialized applicable method is the first one in the list. The classes on which it specializes are retrieved by calling method-specializers (it suffices to retrieve the first one because we already know that both are identical; see listing 11). We then check that the classes of the arguments involved in the generic call (the classes parameter) match the most specific specialization.
( l e t ∗ ( ( method ( c a r methods ) )
( c l a s s ( c a r ( m e t h o d − s p e c i a l i z e r s method ) ) ) ) ( a s s e r t ( equal ( l i s t c l a s s c l a s s ) c l a s s e s ) ) ; ; . . .
Listing 13: Binary completeness check n.1
Next, we have to check that the whole super-hierarchy has properly spe-cialized methods (none were forgotten). This is demonstrated in listing 14. We define a local recursive function find-binary-method that we first apply on the bottommost class in the hierarchy we are checking (the class binding from listing 13). ; ; . . . ( l a b e l s ( ( find−binary−method ( c l a s s ) ( find−method b f ( m e t h o d − q u a l i f i e r s method ) ( l i s t c l a s s c l a s s ) ) ( d o l i s t ( c l s ( r e m o v e − i f # ’( lambda ( e l t ) ( eq e l t ( f i n d − c l a s s ’ s t a n d a r d − o b j e c t ) ) ) ( c l a s s − d i r e c t − s u p e r c l a s s e s c l a s s ) ) ) ( find−binary−method c l s ) ) ) ) ( find−binary−method c l a s s ) )
Listing 14: Binary completeness check n.2
The function find-method retrieves a method meta-object for a particular generic function satisfying a set of qualifiers and a set of specializers. In our case, there is one qualifier: the and method combination type (it can be retrieved by the function method-qualifiers), and the specializers are twice the class of the objects.
By hooking the code excerpts from listings 13 and 14 into the skeleton of listing 12, we have completed our check for the “binary completeness” property.
4
Conclusion
In this paper, we have described why binary methods are a problematic con-cept in traditional object-oriented languages: the relationship between types and classes in the context of inheritance, and the need for privileged access to the internal representation of objects make it difficult to implement.
From the Clos perspective, we have demonstrated that implementing binary methods is a straightforward process, for at least the following two reasons.
1. The covariance / contravariance problem does not exist, because Clos generic functions natively support multiple dispatch.
2. When privileged access to internal information is needed, the dynamic na-ture of Common Lisp provides solutions that are unavailable in statically typed languages. Besides, the package system is completely orthogonal to the object-oriented layer and is pretty liberal in what you can access and how (admittedly, at the expense of breaking modularity just as in other languages).
From the Mop perspective, it is also important to realize that we have not just made the concept of binary methods accessible; we have implemented it directly and explicitly: we have shown ways to not only implement it, but also enforce a correct usage of it, and even a correct implementation of it. To this aim, we have actually programmed a new object system which behaves quite differently from the default Clos. Clos, along with its Mop, is not only an object system. It is an object system designed to let you program your own object systems.
References
[Bruce et al., 1995] Bruce, K. B., Cardelli, L., Castagna, G., Eifrig, J., Smith, S. F., Trifonov, V., Leavens, G. T., and Pierce, B. C. (1995). On binary methods. Theory and Practice of Object Systems, 1(3):221–242.
[Castagna, 1995] Castagna, G. (1995). Covariance and contravariance: conflict without a cause. ACM Transactions on Programming Languages and Systems, 17(3):431–447. [Keene, 1989] Keene, S. E. (1989). Object-Oriented Programming in Common Lisp: a
Programmer’s Guide to Clos. Addison-Wesley.
[Kiczales et al., 1991] Kiczales, G. J., des Rivi`eres, J., and Bobrow, D. G. (1991). The Art of the Metaobject Protocol. MIT Press, Cambridge, MA.