Scala - Generic Functions



We will explain the concept of generic functions in Scala programming in this chapter. You can write flexible and reusable code using generic functions. You can define functions that can operate on various data types with compile-time type safety.

Generic Functions

Generic functions are functions that take type parameters. So, it can operate on objects of different types. These type parameters act as placeholders for the actual data types that will be used when the function is called. So, there can be more flexible and reusable code.

A generic function is defined with one or more type parameters. These type parameters are specified within square brackets after the function name. These type parameters can be used to define the return type and the types of parameters used.

Syntax

The syntax of the generic function is -

def functionName[T](param: T): ReturnType = {
   // function body using type T
}

Example

The following example shows a generic function in Scala programming -

object Demo {
   def printValue[T](value: T): Unit = {
      println(s"Value: $value")
   }

   def main(args: Array[String]): Unit = {
      printValue(42)             // Output: Value: 42
      printValue("Hello, Scala!") // Output: Value: Hello, Scala!
      printValue(3.14)            // Output: Value: 3.14
   }
}

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

Command

> scalac Demo.scala
> scala Demo

Output

Value: 42
Value: Hello, Scala!
Value: 3.14

In the example, the printValue function is defined as a generic function with a type parameter T. The function takes a value of type T and prints it. Demo object calls the printValue function with different types (Int, String, and Double).

Generic Methods within Classes

Generic methods can also be defined within classes. Generic methods can use class type parameters and define their own type parameters.

Syntax

The syntax of generic method inside a class -

class ClassName[T] {
   def methodName[U](param: U): ReturnType = {
      // method body using type U
   }
}

Example

The following example shows a generic method within a generic class in Scala programming -

class Container[T](value: T) {
   def getValue: T = value

   def printValue[U](param: U): Unit = {
      println(param)
   }
}

object Demo {
   def main(args: Array[String]): Unit = {
      val container = new Container[String]("Scala!")
      container.printValue("Scala!") // 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

Scala!

In the example, Container class is defined as a generic class with a type parameter T. The printValue method within the Container class is defined as a generic method with a type parameter U. Demo object calls the generic method with a String type.

Using Multiple Type Parameters

You can define generic functions with multiple type parameters by specifying multiple type parameters within square brackets.

Syntax

The syntax of the generic function using multiple type parameter is -

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

Example

The following example shows a generic function with multiple type parameters in Scala -

def pairToString[T, U](first: T, second: U): String = {
   s"($first, $second)"
}

object Demo {
   def main(args: Array[String]): Unit = {
      println(pairToString(42, "Scala")) // (42, Scala)
      println(pairToString(3.14, true))  // (3.14, true)
   }
}

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, true)

In the example, the pairToString function is defined as a generic function with two type parameters: T and U. Demo object calls generic function with different types (Int and String, Double and Boolean).

Bounded Type Parameters

Bounds can be used in generic functions to restrict the types. These can be used as arguments for a type parameter. This restricts the types that can be used as type arguments.

Syntax

The syntax of the generic function with bounded type parameter is -

def functionName[T <: UpperBound](param: T): ReturnType = {
  // function body using type T
}

def functionName[T >: LowerBound](param: T): ReturnType = {
  // function body using type T
}

Example

The following example shows the use of bounds in generic functions in Scala programming -

class Animal {
   def sound(): Unit = {
      println("Animal sound")
   }
}

class Dog extends Animal {
   override def sound(): Unit = {
      println("Bark")
   }
}

def makeSound[T <: Animal](animal: T): Unit = {
   animal.sound()
}

object Demo {
   def main(args: Array[String]): Unit = {
      val dog = new Dog
      makeSound(dog) // Bark
  }
}

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

Command

> scalac Demo.scala
> scala Demo

Output

Bark

In the example, the makeSound function uses an upper bound T <: Animal. So it restricts the type parameter to be Animal and its subclasses. Demo object calls the generic function with a Dog type.

Context Bounds in Generic Functions

Context bounds are a shorthand way to express a context parameter that depends on a type parameter. For example, a bound like :Ord on a type parameter A of a method and class indicates a context parameter with type Ord(A). Context bounds can be used to reduce code redundancy when passing implicit arguments.

Syntax

The syntax of the context bound in generic function is -

def functionName[T : TypeClass](param: T): ReturnType = {
   // function body using type T
}

Example

The following example shows the use of context bounds in generic functions in Scala programming -

def compare[T : Ordering](a: T, b: T): Int = {
   val ord = implicitly[Ordering[T]]
   ord.compare(a, b)
}

object Demo {
   def main(args: Array[String]): Unit = {
      println(compare(3, 5)) // -1
      println(compare("apple", "banana")) // -1
      println(compare(5, 3)) // -1
      println(compare("apple", "apple")) // -1
   }
}

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

Command

> scalac Demo.scala
> scala Demo

Output

-1
-1
1
0

In the example, the compare function uses a context bound T : Ordering. It indicates that there must be an implicit Ordering[T] in scope. Demo object calls the generic function with Int and String types.

Generic Functions Summary

  • You can define methods with type parameters. So, it provides flexibility and type safety.
  • Generic methods can be defined within classes. These can use class type parameters. These can also define their own type parameters.
  • You can define multiple type parameters and use bounded type parameters to restrict the types that can be used as type arguments.
  • Context bounds express that a type parameter must have an implicit value of a given type in scope.
Advertisements