Scala - Polymorphic Functions



This chapter takes you through the concept of polymorphic functions in Scala programming. You can write code by operating on different types and structures with type safety. Polymorphic functions are also known as generic functions. It enhances code flexibility and reusability.

Polymorphic Functions

Polymorphic functions use type parameters to work with different types with type safety. It is used in writing generic algorithms and data structures.

Definition

Polymorphic function is a function that operates on type parameters. These type parameters are used in square brackets [] and can be used in the function parameters and return type.

Syntax

The syntax of a polymorphic function in Scala is -

def functionName[T](parameter: T): ReturnType = {
  // function body
}

Example

The following example shows defining and using a polymorphic function in Scala programming -

object Demo {
  def identity[T](x: T): T = x

  def main(args: Array[String]): Unit = {
    println(identity(42))      
    println(identity("Scala"))  
  }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

42
Scala

In the example, the identity function is defined as polymorphic with a type parameter T. It returns same value it received. So, there is flexibility in polymorphic functions.

Advantages of Polymorphic Functions

There are various advantages of polymorphic functions. These promote code reuse for same function to operate on different types without rewriting the function for each type. Polymorphic functions also maintain type safety. So, operations on type parameters are valid for all types that can be passed to the function. Polymorphic functions have higher levels of abstraction. So it is easier to write generic algorithms and data structures that can work with any type.

Polymorphic Functions with Collections

Polymorphic functions are used with collections to perform operations that are independent of the specific type of elements in the collection.

Syntax

The syntax of using polymorphic functions with collections is -

def functionName[T](collection: List[T]): ReturnType = {
  // function body
}

Example

Consider the example of using a polymorphic function with collections in Scala programming -

object Demo {
  def reverseList[T](list: List[T]): List[T] = list.reverse

  def main(args: Array[String]): Unit = {
    val numbers = List(1, 2, 3, 4, 5)
    val words = List("Scala", "is", "fun")
    
    println(reverseList(numbers)) 
    println(reverseList(words))   
  }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

List(5, 4, 3, 2, 1)
List(fun, is, Scala)

In the example, the reverseList function is defined as polymorphic with a type parameter T. It reverses the elements of a list. So there is reusability of polymorphic functions with different types of lists.

Multiple Type Parameters

Polymorphic functions can also have multiple type parameters. So you can operate these parameters on multiple types simultaneously.

Syntax

The syntax of polymorphic function with multiple type parameters is -

def functionName[T, U](param1: T, param2: U): ReturnType = {
  // function body
}

Example

Consider the example of a polymorphic function with multiple type parameters in Scala programming -

object Demo {
  def pair[T, U](first: T, second: U): (T, U) = (first, second)

  def main(args: Array[String]): Unit = {
    println(pair(42, "Scala"))        
    println(pair(3.14, List(1, 2, 3)))
  }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

(42, Scala)
(3.14, List(1, 2, 3))

In the example, the pair function is defined with two type parameters, T and U. It creates a pair (tuple) from two values of potentially different types.

Polymorphic Functions with Constraints

Polymorphic functions can include constraints on type parameters, like, requiring a type to be a subtype of another type.

Syntax

The syntax of polymorphic function with constraints is -

def functionName[T <: SuperType](parameter: T): ReturnType = {
  // function body
}

Example

Consider the example of polymorphic function with type constraints in Scala programming -

import scala.reflect.Selectable.reflectiveSelectable

object Demo {
  def printLength[T <: { def length: Int }](item: T): Unit = {
    println(item.length)
  }

  def main(args: Array[String]): Unit = {
    printLength("Hello, Scala")   
    printLength(List(1, 2, 3, 4)) 
  }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

12
4

In the example, the printLength function is constrained to operate on types that have a length method. It will have type safety with flexibility.

Higher-Kinded Types in Polymorphic Functions

Scala supports higher-kinded types. So polymorphic functions can abstract over type constructors (types that take types as parameters).

Syntax

The syntax of polymorphic function with higher-kinded types is -

def functionName[F[_], T](container: F[T]): ReturnType = {
  // function body
}

Example

Consider the example of polymorphic function with higher-kinded types in Scala programming -

object Demo {
  def lengthOfContainer[F[_], T](container: F[T])(implicit ev: F[T] => Iterable[T]): Int = {
    val iterableContainer = ev(container)
    iterableContainer.size
  }

  def main(args: Array[String]): Unit = {
    println(lengthOfContainer(List(1, 2, 3)))     
    println(lengthOfContainer(Array(1, 2, 3, 4))) 
  }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

3
4

In the example, the lengthOfContainer function abstracts over type constructors and calculates the size of a container.

Higher-Order Polymorphic Functions

Higher-order polymorphic functions either take other functions as parameters or return functions as results or both.

Syntax

The syntax of the higher order polymorphic function is -

def higherOrderFunction[T, U](input: T, f: T => U): U = {
  f(input)
}

Example

The following example demonstrates a higher-order polymorphic function in Scala programming -

def applyFunction[T, U](value: T, f: T => U): U = {
  f(value)
}

object Demo {
  def main(args: Array[String]): Unit = {
    val result1 = applyFunction(10, (x: Int) => x * 2)
    val result2 = applyFunction("Scala", (s: String) => s.toUpperCase)
    
    println(result1)
    println(result2)
  }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

20
SCALA

In the example, applyFunction is a higher-order polymorphic function. It takes a value of type T and function f that operates on T and returns U. Demo object shows how this function can be used with different types (Int and String) and functions (Int => Int and String => String).

Polymorphic Functions Summary

  • Polymorphic functions use type parameters to operate on different types with type safety.
  • These promote code reusability, type safety, and abstraction.
  • You can work with collections using polymorphic functions. These can have multiple type parameters, like, type constraints, higher-kinded types, etc.
Advertisements