Introduction to Programming Languages/Universal Polymorphism

Universal Polymorphism
Symbols that are universally polymorphic may assume an infinite number of different types. There are two kinds of universal polymorphism: parametric and subtyping. In the rest of this chapter we will see these variations in more detail.

Parametric Polymorphism
Parametric polymorphism is a feature of routines, names or symbols that can be parameterized in one or more types. This kind of polymorphism lets us to define codes that are generic: they can be instantiated to handle different types. The code below shows the use of a template, the way to implement parametric polymorphism in C++.

The program above defines a polymorphic function called  (lines 3 to 8). The type variable, defined in the scope of  , will be replaced by an actual type during the function call. The main function shows two calls to. The call at line 13 uses the type  whereas at line 14 it uses the type. The arguments of  are compared using the " " operator. Therefore, to use this function, it is necessary that the actual type that replaces T implements this kind of comparison. Fortunately, C++ allows us to define this operator to our own types. As an example, the user defined class, shown below, is a valid type to  , as it implements the greater-than operator:

Parametric polymorphism is present in many different statically typed languages. As an example, the function below, implemented in Java, manipulates a list of generic types. Notice that, even though C++ and Java share similar syntax, parametric polymorphism in these languages is implemented in different ways. In C++ templates, each instance of a parametric function is implemented separately. In other words, the C++ compiler generates a whole new function for each specialization of a polymorphic function. Java's generics only create one implementation for each parameterized function.

SML implements parametric polymorphism in a way that is similar to Java. Only one instance of each parametric function exists in the entire program. These functions manipulate references to values, instead of the values themselves. The function below, for instance, computes the length of a generic list in SML. Notice that our implementation does not need to known anything about the values stored in the list. It only manipulates the structure of this list, considering any type stored there as a generic reference.



Parametric polymorphism gives us the idea of a type constructor. A type constructor is a kind of function that receives types, and produces new types. For instance, in the Java program above, we saw the type constructor. We cannot instantiate a Java object with this type. Instead, we need to use a specialization of it, such as, for instance. So, instantiating  with the type , for instance, is analogous to passing this type to a single-parameter function   that returns back.

Parametric polymorphism is an important mechanism of code reuse. However, not every programming language provides this feature. Parametric polymorphism is absent, for instance, from widely used languages, such as C, Fortran or Pascal. Nevertheless, it is still possible to simulate it using several different strategies. For example, we can simulate parametric polymorphism in C using macros. The program below illustrates this technique. The macro  has a type parameter, similarly to a type constructor. We have instantiated this macro twice, first with, and then with.

Subtyping Polymorphism
A well-known property present in object oriented languages is the Liskov's Substitution Principle. This principle says that in any situation in which the left-hand-side of an assignment expects a type T, it can also receive a type S, as long as S is subtype of T. Programming languages that follow the Liskov's Substitution Principle are said to provide subtyping polymorphism. The program below, written in Java, illustrates this kind of polymorphism. The three classes, String, Integer and LinkedList are subclasses of <tt>Object</tt>. Therefore, the function <tt>print</tt> can receive, as actual parameters, objects that are instances of any of these three classes.

Subtyping polymorphism works because if <tt>S</tt> is subtype of <tt>T</tt>, then <tt>S</tt> meets the contract expected by <tt>T</tt>. In other words, any property of the type <tt>T</tt> is also present in its subtype <tt>S</tt>. In the example above, the function <tt>print</tt> expects types that "know" how to convert themselves into strings. In Java, any type that has the property <tt>toString</tt> has this knowledge. Given that this property is present in the class <tt>Object</tt>, it is also present in all the other classes that, according to the language's semantics, are subtypes of <tt>Object</tt>.



There are two basic mechanisms that programming languages use to define the subtype relation. The most common is nominal subtyping. Languages such as Java, C#, C++ and Object Pascal are all based on nominal subtyping. According to this system, the developer must explicitly state, in the declaration of <tt>S</tt>, that <tt>S</tt> is subtype of <tt>T</tt>. As an example, the code below illustrates a chain of subtypes in the Java programming language. In Java, the keyword <tt>extends</tt> is used to determine that a class is subtype of another class.

The other mechanism used to create subtyping relations is structural subtyping. This strategy is less common than nominal subtyping. One of the most well-known programming languages that foster structural subtyping is ocaml. The code below, written in this language, defines two objects, <tt>x</tt> and <tt>y</tt>. Notice that, even though these objects have not being explicitly declared with the same type, they contain the same interface, i.e., they both implement the methods <tt>get_x</tt> and <tt>set_x</tt>. Thus, any code that expects one of these objects can receive the other.

For instance, a function  can receive either <tt>x</tt> or <tt>y</tt>, e.g.,   and    are valid calls. In fact, any object that provides the property <tt>set_x</tt> can be passed to, even if the object has not the same interface as <tt>x</tt> or <tt>y</tt>. We illustrate this last statement with the code below: In other words, if an object <tt>O</tt> provides all the properties of another object <tt>P</tt>, then we say that <tt>O</tt> is subtype of <tt>P</tt>. Notice that the programmer does not need to explicitly state this subtyping relation.

In general, if <tt>S</tt> is a subtype of <tt>T</tt>, then <tt>S</tt> contains more properties than <tt>T</tt>. For instance, in our class hierarchy, in Java, instances of <tt>Mammal</tt> have all the properties of <tt>Animal</tt>, and, in addition to these properties, instances of <tt>Mammal</tt> also have the property <tt>suckMilk</tt>, which does not exist in <tt>Animal</tt>. The figure below illustrates this fact. The figure shows that the set of properties of a type is a subset of the set of properties of the subtype.



Nevertheless, there exist more instances of the supertype than instances of the subtype. If <tt>S</tt> is a subtype of <tt>T</tt>, then every instance of <tt>S</tt> is also an instance of <tt>T</tt>, whereas the contrary is not valid. The figure below illustrates this observation.