Realization of Multimethods in Single Dispatch Object Oriented Languages
Total Page:16
File Type:pdf, Size:1020Kb
Realization of Multimethods in Single Dispatch Object Oriented Languages Rajeev Kumar, Vikram Agrawal and Anil Mangolia Department of Computer Science and Engineering Indian Institute of Technology Kharagpur Kharagpur, WB 721302, India [email protected] Abstract The need for multimethods is preeminent in the domain of object-oriented programming. However, multimethods are far from universally acknowledged. Commonly used languages – C++, Java & C# – are single dispatching languages. In this paper, we demonstrate the alternatives for multimethods in such languages. The techniques discussed, in this paper, include double dispatching (a well known technique), technique of message dispatching using RTTI (explored in detail by us in this paper), and reflection (a feature provided by some compilers). We implement these techniques for C++, Java and C# languages and discuss issues associated with encapsulating multimethods in single dispatch languages. We also compare techniques in terms of their efficiency, ease of (re)use and type safety. Keywords: object-oriented programming, message dispatch, single dispatch, multiple dispatch, multimethods. 1. Introduction Polymorphism is one of the central features of the object-oriented programming. Depending on the number of polymorphic arguments participating in method lookup, object-oriented languages can be classified into: (i) single dispatch languages where only receiver or one argument participates, and (ii) multiple dispatch languages where more than one argument participates. The methods with more than one polymorphic argument are called multimethods. Commonly used languages e.g., C++, C# and Java are single dispatch languages where as languages like Cecil [Cha92] and CLOS [Bob+88] are multiple dispatch languages. The need for multimethods expressed by the programs suggests inclusion of the multimethods in single dispatch languages. For example, following are few cases where this problem manifests itself in diverse applications: 1. A standard binary arithmetic such as Add(Operand o1, Operand o2) requires the message to be dispatched on both operands o1 and o2. If the data types supported are fixed-precision integers and floating-point numbers, the method for adding two fixed-precision integers is different from that for adding two floating numbers which in turn is different from that for mixed mode addition. Similarly, most data types define an equality testing binary operation and ordering relations. 2. The message PaisDo(Collection c1, Collection c2, Block b) takes the collections, c1 and c2, and a closure block b and iterates through the two collections in parallel. Neither collection is more important than the other is. However, in a single-dispatching language, the programmer has no choice but to favor one over the other. 3. In a graphics application, the message Display(Shape s, Device d) is intended to display a Shape s on an output Device d. However, drawing a rectangle is different from drawing a spline and an X-window screen uses a different rendering strategy for a filled polygon than does a fancy graphics accelerator. Thus, the exact method to be invoked depends upon both, the shape and the display device. There are two ways of adding multimethod support to a single dispatch language, one, make changes in language syntax and compiler, and, two, simulate realization of multimethods on the existing single dispatch languages indirectly. Simulation has advantage that no change is required in the compiler. In this paper, we discuss simulation of multimethods in single dispatch languages using the following three techniques: x Double dispatching – Single dispatching on each polymorphic arguments in turn, x Using runtime type identifiers – Using type of polymorphic arguments and method lookup routine in program, and x Reflection – Using named method invocation and changing message dispatch with computational reflection. ACM SIGPLAN Notices 18 Vol. 40(5), May 2005 The remainder of this paper is organized as follows. In Section 2, we briefly review the basics of object-oriented languages and the need of multimethods for elegant programming. In Section 3, we present how to realize multimethods in single dispatch languages. In Section 4, we discuss language features and their support in realizing multimethods. Finally, we conclude in Section 5. 2. Object Oriented Techniques 2.1 Preliminaries The mechanism for performing a task in object-oriented programming involves finding an appropriate agent termed as an object and giving to this agent a message that describes the request [Bud87, Str97]. The message is accompanied by the additional information needed to perform the action. The receiver is the agent to whom the message is sent. If the receiver accepts the message, it bears the responsibility to carry out the indicated action. In response to a message, the receiver executes some method to satisfy the request. The idea of message in object-oriented programming is closely associated with the concept of encapsulation or information hiding. A class t defines instance variables (or data members) and methods (or member functions) of some objects. Objects conforming to this definition are referred to as instances of the class. The notion of a class enables the programmer to describe the common features of a concept, while still allowing each object of the class to have a sense of the individuality. For example, a shape is a concept. We express the features of a shape by defining a shape class in Fig. 1. class Shape instance variables center : Point methods rotate(angle: integer) -- Rotate the shape by an angle end class Fig. 1: Definition of Shape class We may now create instances (i.e. objects) of the shape class. To perform the task of rotating a shape, we send the rotate message to the shape object that we wish to rotate. The additional information supplied is the angle by which the shape needs to be rotated. The methods of a class solely have privileged access to the instance variables of the class. Only the interface provided by the methods is externally visible. Thus, when a message is transmitted to a receiver, the sender does not really care how the request is processed. It is entirely the responsibility of the receiver to interpret the message and invoke the appropriate method to perform the desired action. On the other hand, an abstract class is a class of which there are no direct instances; it helps in abstraction and is used to create subclasses. With reference to the example in Fig. 1, it would be meaningless to create an instance of the shape class; a ‘shapeless’ shape is hardly of any use. It makes more sense to create an instance of a specific shape as a circle or a square. Knowledge of a more general class is also applicable to a more specific class through inheritance. A subclass or child class that extends a class or classes (called the superclasses or parent classes of the subclass), is said to inherit the attributes of its superclasses. Inheritance applies to the definitions of both instance variables and methods. Both instance variables and methods defined in a parent class become associated with a child class and are available for use by instances of the child class. For example, a circle is a shape, so is a square. Therefore, it is natural to express the circle and square classes as subclasses of the shape class. These classes are organized into a hierarchical inheritance structure. Such hierarchies are typical of object-oriented programming and can grow significantly largeer at times. The concept of inheritance not only enables to share common features, but also allows certain features to be overridden. A subclass can override the definitions of methods that it would otherwise inherit by redefining them. For example, the circle class provides its own rotate method, overriding the rotate method provided by the shape class. So does the square class. However, they both inherit other features from the shape class. Object-oriented programming speeds up the program development and improves the maintenance, reusability and modifiability of software if the object-oriented techniques are properly used. For example, a variable that is declared as a shape, during the course of execution, can take on many different forms, holding an instance of a circle at one instant, and an instance of a square at the other. This notion of accepting an instance of a class where an instance of its superclass is expected ACM SIGPLAN Notices 19 Vol. 40(5), May 2005 is called substitutability principle. By substitutability principle, an instance of a subclass can always be used in any context in which an instance of a superclass can be used, and this phenomenon triggers polymorphic behavior. Polymorphism is used to describe the notion that the type of a receiver is distinguished from the value that it holds at a particular instant. 2.2 Motivation for Multimethods We explain multimethods with reference to the example below. For simplicity, we assume the existence of only two kinds of shapes (viz. circle and square) in the inheritance hierarchy. class Shape -- An abstract class instance variables center : Point methods abstract draw(s :Shape) -- Draw shape s abstract intersect(s1:Shape, s2:Shape) -- Intersect shape s1 with shape s2 end class Fig. 2: Definition of Shape class using multimethod In most object oriented programming languages, a message is sent to one distinguished receiver object. The run-time type of the receiver determines the method that is invoked by the message. Other arguments of message (if any) are passed to the invoked method and thus, do not participate in method-lookup (also known as dispatching). This style of object-oriented programming is termed as single dispatching, since method-lookup is performed only with respect to the single receiver argument. Single dispatching works fine for many kinds of messages, especially those in which one argument is more special than the rest, in the sense that this argument alone determines the method to be involved.