Copyright (C) 2016, H. Conrad Cunningham
Acknowledgements: I adapted this short note on the Kinds of Polymorphism from my notes from the Spring 2016 offering of CSci 555, Functional Programming (which used Scala).
In writing this section, I benefited from recently reading the good Wikipedia articles on Polymorphism, Ad Hoc Polymorphism, Parametric Polymorphism, Subtyping, and Function Overloading as well as my previous study of the topic in the contexts of object-oriented, functional programming, multiparadigm programming, and software architecture over the past 25 years.
Polymorphism refers to the property of having "many shapes". In programming languages, we are primarily interested in how polymorphic function names (and operator symbols) are associated with implementations of the functions.
In general, two primary kinds of polymorphism exist in programming languages:
Ad hoc polymorphism, in which the same function name (or operator symbol) can denote different implementations depending upon how it is used in an expression. That is, the implementation invoked depends upon the types of function's arguments and return value.
There are two subkinds of ad hoc polymorphism.
Overloading refers to ad hoc polymorphism in which the language's compiler or interpreter determines the appropriate implementation to invoke using information from the context. In statically typed languages, overloaded names and symbols can usually be bound to the intended implementation at compile time based on the declared types of the entities. They exhibit early binding.
Java overloads a few operator symbols, such as using the +
symbol for both addition of numbers and concatenation of strings. Java also overloads calls of functions defined with the same name but different signatures (patterns of parameter types and return value). Java does not support user-defined operator overloading; C++ does.
Subtyping (also known as subtype polymorphism, inclusion polymorphism, or polymorphism by inheritance) refers to ad hoc polymorphism in which the appropriate implementation is determined by searching a hierarchy of types. The function may be defined in a supertype and redefined (overridden) in subtypes. Beginning with the actual types of the data involved, the program searches up the type hierarchy to find the appropriate implementation to invoke. This usually occurs at runtime, so this exhibits late binding.
The object-oriented programming community often refers to inheritance-based subtype polymorphism as simply polymorphism.
Parametric polymorphism, in which the same implementation can be used for many different types. In most cases, the function (or class) implementation is stated in terms of one or more type parameters. In statically typed languages, this binding can usually be done at compile time (i.e., exhibiting early binding).
The object oriented programming community often calls this type of polymorphism generics or generic programming. The functional programming community often calls this simply polymorphism.
In a dynamically typed language like Lua, the parameters and return values of functions are dynamically typed. So a function can often take a variety of types for an argument. However, the function body may sometimes need to carry out explicit type checks to be able to perform the operation appropriately.