This article is from the Object-Oriented Technology FAQ, by Bob Hathaway email@example.com with numerous contributions by others.
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:
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
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.