Multi-methods involve two primary concepts, multiple-polymorphism and lack of encapsulation. These issues are orthogonal. Multiple-polymorphism implies more than one parameter can be used in the selection of a method. Lack of encapsulation implies all arguments can be accessed by a multi-method (although packages can be used to restrict access, as in CLOS). Multi-methods can also imply a functional prefix notation, although the CLOS designers (who coined the term "multi-method") consider the functional and receiver based forms (messages) equivalent. Functional syntax was chosen "in order to minimize the number of new mechanisms added to COMMON LISP" [Kim ch 4, p70 (D. Moon)]. [Chambers 93] discusses multi-methods in his new OO language, Cecil.
Multiple-polymorphism allows specialized functions or methods to be defined to handle various cases:
+(int, int) +(int, float) +(int, complex) +(int, real) +(float, complex) +(float, real) +(float, float)
The above functions are specialized to each of the cases required allowing single, highly cohesive and loosely coupled functions to be defined. This is also the true essence of object-oriented polymorphism, which allows objects to define methods for each specific case desired. In addition to better coupling and cohesion, multiple-polymorphism reduces program complexity by avoiding coding logic (switch statements) and because small methods further reduce complexity, as code complexity doesn't grow linearly with lines of code per method, but perhaps exponentially. This should be distinguished from double dispatch, a fancy name for single dispatch after a call, which only provides switching on a single argument per call (but for 2 levels), consistently ignoring the inherent type of parameters in messaging. Double dispatch is used in languages with static typing for simplicity and efficiency considerations.
If all of the above types are Numbers, code can be written without concern for the actual classes of objects present:
fn(one, two: Number): Number return one + two;
The addition expression above will invoke the correct "+" function based on the inherent (true, actual, or dynamic) types of one and two. Only the inherent type of "one" would be used with double dispatch! In the author's opinion, this is a serious shortcoming. Further, double dispatch would only allow switching to the "fn" function based on the type of "one" also. This could lead to the use of switch statements based on type or complex coding in many real-world programming situations, unnecessarily. In the author's opinion, this should only be used as necessary, e.g. if the implementation language doesn't support multiple-polymorphism and either efficiency considerations dominate and double dispatch can be suffered, or an embedded dynamic typing scheme is used.
Why do multi-methods allow open access to parameters? It allows efficient handling, like C++ friends, usually by allowing representation details of more than one object to be exposed. See [Kim ch 4, pp70-71 (D. Moon)] for an alternative explanation. While open access can be useful in some cases, it typically isn't recommended as a general OO practice (see section 1.15, C+W's requirement 1 for OO languages and Section 1.2 on Encapsulation) and also violates subtype polymorphism, because only subclass polymorphism is based on representation and not type.
Polymorphic languages can be statically typed to provide strong type checking, efficiency, and to support a static programming idiom, but require restrictions in many cases, such as requiring overriding methods to have identical signatures with the methods they substitute (as in C++) or allowing covariant parameters but limiting base class usage (as in Eiffel). If these restrictions are dropped, multiple-polymorphism results. Thus a single overridable function declared in a base class may have several functions overriding it in a derived class differentiated only by their formal argument types. This therefore requires both static and dynamic typing, because no formal argument differentiation is possible without static types, as in Smalltalk, and no actual argument differentiation is possible without dynamic types (as in C++ and Eiffel). See section 2.3 for another example of multiple-polymorphism.
There is some concern about the efficiency of run-time method selection as can occur with multiple-polymorphism (or even dynamic message passing). However, static analysis optimizations are commonly available in the literature, potentially providing a single static selection in many cases [See Agrawal 91, Chambers 92, Mugridge 91, and etc.].
But coupling the two cases of selector variables (as found in CLOS, Objective-C, and etc.) and several possible known selectors together with the general undecidability of dynamic types at compile-time renders dynamic typing and run-time selection (or checking) as unavoidable in the general case [a point often mistaken in comp.object. E.g. simple statically/strongly typed multi-methods still require dynamic types!]
See [Booch 91], multiple-polymorphism, for a good CLOS example.
This document was translated by ms2html v1.8 on 01.06.95.