Polymorphic functions in Scala
1. Introduction Link to heading
A Polymorphic function is a very common term in Functional Programming.
In Object-Oriented Programming (OOP) also exists the concept of “Polymorphism” but it refers to objects and inheritance.
A polymorphic function is a function that can work for any data type it’s given. Polymorphic functions are also known as Parametric polymorphism.
As you can see, the definition is very simple: “A function that accepts any type”. But first, let’s start with the basics.
Note: You can copy/paste the code and run it in the Scala REPL. The code is written in Scala 3 new syntax but we are not using any new Scala 3 feature.
2. Functions Link to heading
2.1 Monomorphic function Link to heading
I want to start with the implementation of a function that checks whether a list of integers List[Int]
is sorted. Something like this:
|
|
isSorted
is a function that given a list of integers returns true
only if the elements are sorted in ascending order, otherwise, it returns false
.
The implementation of isSorted
consists of a local recursive function loop
that verifies that the elements of the list are sorted.
Let’s try with some lists of integers in the Scala REPL:
|
|
The function is working as expected. isSorted
receives a list of integers and verifies that the elements of the list are sorted. So far, isSorted
is a monomorphic function. It can only process lists of integers. If I’d like to verify if a list of floating-point numbers is sorted List[Double]
, I couldn’t use isSorted
because the Scala compiler would throw an error. Let’s try:
|
|
To fix this problem, we can write another function for lists of doubles def isSorted(elements: List[Double])
but the problem with this approach is that we will duplicate the code for a different type and what if we want to apply the same functionality to lists of booleans or lists of strings?. This alternative is not going to scale.
Another approach is to make isSorted
a Polymorphic function which basically can process a list of any type.
2.2 Polymorphic function Link to heading
To make isSorted
a Polymorphic function, we need to apply some changes.
What we are going to do first is to “parametrize” the function by adding the parameter type A
between brackets after the name of the function, so that the Scala compiler detects this is a Polymorphic function: isSorted[A]
. This new type parameter A
can be used in the parameters of our function and will allow us to define the list parameter as List[A]
which can be a list of any type.
Another change we need to do is to define a second parameter which is a function that is going to be used to verify if two elements of the list are ordered. As you can see, this new parameter is required because now we can process any type, so we can’t use integer comparison (<, >, <=, >=
) anymore, but Scala allows us to define functions as parameters and in this case, we can let users provide their custom comparison function according to the type they want to verify. This brings flexibility and scalability to our code.
|
|
This is the implementation of our Polymorphic function isSorted
: 🎉
|
|
Now I can apply isSorted
to a list of integers:
|
|
(current, next) => current < next
is a lambda function or anonymous function that is used internally in isSorted
to verify two elements of the list (current and next), if current
is less than next
which means those two elements are sorted, and if is applied to each element of the list.
The flexibility of this approach is such that I can also apply isSorted
and verify if the order is descendent List(3, 2, 1)
. I only need to provide a different ordered
function. Let’s do it:
2.2.1 Verifying sorting in descendent order Link to heading
In this case, we create the descendentOrder
variable and assign it the lambda, just to show you that you can also define a lambda and assign it to a variable, even though you can also return it as a result of a function because functions and lambdas are high order citizens in Scala.
|
|
Let’s use descendentOrder
function in our isSorted
function.
|
|
As you can see, making the function polymorphic provides us with better flexibility and scalability. Now we can define the criteria we want to use to compare the elements of the list and verify if the list complies or not with the given criteria.
2.2.2 Verifying sorting in a list of Double Link to heading
Let’s verify a list of doubles:
|
|
It works! 😄
2.2.3 Verifying sorting in a list of Boolean Link to heading
How about booleans?
|
|
It also works!
2.2.4 Verifying sorting in a list of String Link to heading
We can even apply the same comparison function to a list of strings due to Scala has an implementation for comparison operators for String
:
|
|
2.2.4.1 Verifying sorting based on string’s length Link to heading
What if we want to verify if a list of strings is sorted by the length of its strings elements?
List("a", "aa", "aaa")
is true
?
It’s possible, but we need to provide a different comparison function:
|
|
and then:
|
|
3. Conclusion Link to heading
In this post, we explored a powerful technique in the Functional Programming paradigm.
First, we started analyzing a simple function that verifies if a list of integers is sorted or not. This function is pretty much similar in structure and functionality to any other functions that we write in our daily job. Then we continued applying some changes to make it more flexible and scalable using the Polymorphic Function paradigm through generic type parameters.
Polymorphic Functions can be used to reduce code duplication and generalize the behavior of a function for a common set of types. We just need to start considering an initial type and then start iterating over the next types we want to support.