The word polymorphism can mean different things depending on the context. When programmers in imperative languages talk about polymorphism, they usually mean polymorphism of subtypes. At the same time, programmers in functional languages have parametric polymorphism in mind. Let us talk about the latter.
We will see Java code in this lesson. Do not worry if you don't fully understand it. Our goal is to deal with concepts, not Java itself.
The lodash library has the _.concat()
function, which combines arrays passed to it:
_.concat([1], [2, 3, 1]); // [1, 2, 3, 1]
_.concat(['one'], ['two', 'three']); // ['one', 'two', 'three']
_.concat([true], [false, false, true]); // [true, false, false, true]
This function joins any array, regardless of the type of data contained within. Let's try to implement it ourselves.
It is a slightly stripped-down version of the concat
function. It only works with two arguments, each of them is an array. The function creates a new array, then traverses the passed arrays one by one, and adds their values to the newly created array.Then, it returns the result:
const concat = (coll1, coll2) => {
const result = [];
coll1.forEach((value) => result.push(value));
coll2.forEach((value) => result.push(value));
return result;
};
Take a close look at this code. Does it perform any operations on the data inside the array? The correct answer is no. This data moves from one array to another, but we do not act on it. Our new concat()
function, like the original _.concat()
function, can handle arrays containing any data type.
This behavior seems natural for developers who've only written in dynamic languages. But in static languages, it's not so simple. Below is an example of the definition of arrays in Java:
int numbers[] = {3, 1, 2, 5, 4};
String words[] = {"one", "two", "three"};
The first thing you might notice is the need to specify the type. For the first array, it is int
. For the second one, it is String
. You can't create an array without specifying what type its values will have. The same applies to functions that process arrays:
class Main {
public static void main(String[] args) {
// Declaring array `a`
int[] a = {1, 2, 3, 4};
// Declaring array `b`
int[] b = {4, 16, 1, 2, 3, 22};
// Merging arrays
concat(a, b);
}
// Arrays are containing only int can be input
public static int[] concat(int[] arr1, int[] arr2) {
// Create a resulting array whose length is equal to the sum of the lengths of the original arrays
int[] result = new int[arr1.length + arr2.length];
// Transfer all values from the first array to result
for (int i = 0; i < arr1.length; i++) {
result[i] = arr1[i];
}
// Transfer all values from the second array to result
for (int j = 0; j < arr2.length; j++) {
result[arr1.length + j] = arr2[j];
}
return result;
}
}
Note the signature of the concat() method: concat()
: int[] concat(int[] arr1, int[] arr2)
. In contrast to the JavaScript variant, the input parameters here are arrays of numbers. I.e., this function won't work for an array of strings. Nor will it work for all other data types.
What does this mean in practice? Something simple but sad. We have to implement a similar function for each type even though the algorithm inside is identical.
It is where parametric polymorphism comes in handy. Static languages have to introduce special constructions into the language. They help us describe these algorithms without regard to the type of parameter. In some languages, they're called templates (C++) or generics (Java, C#):
class Main {
public static void main(String[] args) {
Integer[] a = {1, 2, 3, 4};
Integer[] b = {4, 16, 1, 2, 3, 22};
concat(a, b);
}
public static<T> T[] concat(T[] arr1, T[] arr2) {
T[] result = (T[]) new Object[arr1.length + arr2.length];
for (int i = 0; i < arr1.length; i++) {
result[i] = arr1[i];
}
for (int j = 0; j < arr2.length; j++) {
result[arr1.length + j] = arr2[j];
}
return result;
}
}
In this code, we see a type called T. It simply means we can use it within an array with any type. The concat()
method now works like its JavaScript counterpart.
Parametric polymorphism makes it possible to write generalized algorithms for composite types. In some cases, it reduces the amount of code. Sometimes, this comes at the expense of having a simple solution. But you won't see an increase in complexity for most typical operations. You can see that in the code above.
Dynamic languages don't need parametric polymorphism to implement generalized algorithms. Any collection can contain any data type at any time. It eliminates the need to introduce additional language constructs and learn new concepts.
In the literature, the usage of parametric polymorphism is often called generalized programming.
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.