Polymorphism in JavaScript
For the longest time, I thought that “Polymorphing” was about converting something into sheep (thanks to Warcraft). The sheep image stuck with me and made it hard to understand exactly what Polymorphism is.
Today I want to explore what Polymorphism actually is. (Fun fact: Most articles about Polymorphism in JavaScript covers less than 1/3 of what it actually is).
What is Polymorphism
Polymorphism comes from the word Polymorph.
- Poly: Many.
- Morph: Change from one form to another.
So Polymorphism is the ability to take on multiple forms.
**There are three kinds of Polymorphism in programming: **
- Adhoc Polymorphism
- Parametric Polymorphism
- Subtype Polymorphism
Most articles on Object Oriented Programming and Polymorphism explains the 3rd type only. They don’t explain the other two.
Adhoc polymorphism
Adhoc is used to describe creation of something without previous planning. In other words, Adhoc Polymorphism means to change something from one form to another on the spot.
There are many forms of Adhoc Polymorphism
- Operator Overloading
- Function Overloading
- Coercion Polymorphism
Operator Overloading
Overloading means being able to do more than one thing.
Example:
The +
operator in JavaScript does many things. You can use it to add numbers. You can also use it to concatenate strings.
// Adding numbers
1 + 1 // Results in 2
// Adding Strings
'Hello' + ' ' + 'World' // Results in 'Hello World'
// Adding Numbers to Strings
1 + 'up' // Results in '1up'
The type
of the result changes depending on what’s being added.
Number
+Number
createsNumber
Number
+String
createsString
The +
operator in this case, allows you to change the values from one form (like Number
) to another (like String
).
Function Overloading
In some programming languages, Function overloading means creating two (or more) functions with the same name. Each function does something different depending on the arguments given to it.
Example from Wikipedia on calculating Volume in C++:
// Volume of a Cube.
int Volume(int s) {
return s * s * s;
}
// Volume of a Cuboid.
long Volume(long l, int b, int h) {
return l * b * h;
}
Function Overloading in JavaScript is slightly different because we cannot produce two different functions with the same name.
We use one function, but we change the results according to the arguments we receive.
The above example could be rewritten as follows in JavaScript:
function volumeCuboid (length, breadth, height) {
return length * breadth * height
}
function volumeCube (length) {
return volumeCuboid(length, length, length)
}
// Overloading happens here
function calculateVolume (...args) {
if (args.length === 3) return volumeCuboid(...args)
return volumeCube(args[0])
}
We don’t need to depend on the number of arguments. We can also change the result depending on the value of each argument.
Example:
We can have a createShape
function that returns different objects depending on value of shape
. (The Factory Pattern uses this type of Polymorphism).
function createShape (size, shape) {
if (shape === 'triangle') return new Triangle(/* ... */)
if (shape === 'rectangle') return new Rectangle(/* ... */)
if (shape === 'square') return new Square(/* ... */)
}
(Fun Fact: I understood this version of Polymorphism from Martin Fowler’s Refactoring: Improving the Design of Existing Code. It made me a little more curious about Polymorphism, which eventually led to this article you’re reading!)
If we distill this theory down further, all if
and switch
statements result in Function Overloading.
function createEmoji (emotion) {
if (emotion === 'happy') return '😃'
if (emotion === 'sad') return '😞'
return 😑
}
Coercion Polymorphism
JavaScript has Type coercion. It converts value from one type to another while evaluating them.
For example, you can any expression inside an if
statement. JavaScript converts expression into true
or false
. If the expression converts to true
, the expression is said to be truthy. If the expression converts to false
, the expression is said to be falsey.
const string = 'hello'
if (string) {
console.log(string)
}
Another example: You can compare strings and numbers with ==
(although this is generally not recommended).
22 == '22' // true
Since type coercion happens on the spot, it is a form of adhoc polymorphism.
Variable overloading?
I’m not sure about this one.
Wikipedia defines Polymorphism as this:
Polymorphism is the provision of single interface to entities of different types, or the use of a single symbol to represent different types
“Use a single symbol to represent different types” feels like Variable overloading to me. (Variable Overloading is not an actual term. It’s something I came up with).
We already overload variables in JavaScript since each variable can represent any value.
// Variables in JavaScript can represent any value
const str = 'string'
const num = 123
const bool = true
const array = []
const obj = {}
const nah = null
Parametric Polymorphism
Parametric Polymorphism is polymorphism related to parameters… But that’s not very useful so let’s describe what it’s about.
Parametric Polymorphism has two parts:
- Data that can contain many types of data
- Functions that can work with many types of data
Data that can contain many types of data
Everything in JavaScript is an Object. So Objects are parametric. It can be converted into other types of data.
Objects can also store multiple types. It doesn’t care what values are stored.
const object = {
str: 'hello',
num: 123,
bool: true
}
Arrays are also parametric. It lets you store many types of data and it doesn’t care what they are.
const array = ['hello', 123, true]
Functions that can work with many types of data
Functions that can work with many types of data are called polymorphic functions. They don’t care what comes in. They’ll apply the transformation they’re told to do, and they spit out a result.
map
is a good example. It takes in an array and spits out another array. It doesn’t care what goes in-between.
const doubled = [1, 2, 3].map(num => num * 2)
You can use map
to convert numbers into strings.
const toString = [1, 2, 3].map(num => `${num}`)
Object.assign
is another example. It takes in an object and spits out another object, but it doesn’t care what goes inside each object.
Object.assign({}, { property: 'value'})
Subtype Polymorphism
Subtype Polymorphism involves creating derivative objects from a parent object. It’s can be called Inclusion Polymorphism, Subclassing, or Inheritance. (Inheritance is such a loaded word. I’ll explain it another day).
Derivatives objects can then override a method from the parent and it’ll still work.
Example:
Let’s say you have a Human
class with a sayHi
method:
class Human {
constructor(name) {
this.name = name
}
sayHi() {
console.log(`Hi! My name is ${name}`)
}
}
Then you create a Developer
and Designer
subclass from Human
.
class Developer extends Human {/* ... */}
class Designer extends Human {/* ... */}
We Designer
and Developer
to talk more about themselves, so we can override the sayHi
method.
class Developer extends Human () {
sayHi() {
console.log(`Hi! My name is ${name}. I am a developer.`)
}
}
class Designer extends Human () {
sayHi() {
console.log(`Hi! My name is ${name}. I am a designer.`)
}
}
Now you have three different classes. Each one of them can sayHi
. You can use sayHi
normally and they’ll all work, but they produce different results.
const zell = new Human('Zell')
const vincy = new Developer('Vincy')
const tim = new Designer('Tim')
zell.sayHi() // Hi! My name is Zell.
vincy.sayHi() // Hi! My name is Vincy. I am a developer.
tim.sayHi() // Hi! My name is Tim. I am a designer.
That’s it!
Wrapping up
There are the three kinds of Polymorphism.
- Adhoc Polymorphism
- Parametric Polymorphism
- Subtype Polymorphism
Chances are, you’re already using Polymorphism without knowing it 😉. I hope this clears up Polymorphism for you!