Scala Tutorial

Basics

Control Statements

OOP Concepts

Parameterized - Type

Exceptions

Scala Annotation

Methods

String

Scala Packages

Scala Trait

Collections

Scala Options

Miscellaneous Topics

Scala | Polymorphism

In Scala, as in many other programming languages, polymorphism is a core principle of object-oriented programming. It allows objects of different types to be treated as if they were objects of the same type. Scala supports both compile-time (static) and runtime (dynamic) polymorphism.

1. Subtype Polymorphism (Runtime/ Dynamic Polymorphism):

Subtype polymorphism, often just referred to as "polymorphism" in the context of OOP, is when a name denotes instances of many different classes related by some common superclass.

For example:

abstract class Animal {
  def speak: String
}

class Dog extends Animal {
  def speak: String = "Woof"
}

class Cat extends Animal {
  def speak: String = "Meow"
}

def animalSound(a: Animal): String = a.speak

val dog: Animal = new Dog
val cat: Animal = new Cat

println(animalSound(dog))  // Outputs: Woof
println(animalSound(cat))  // Outputs: Meow

Even though the type signature of animalSound specifies that it accepts an object of type Animal, it can also accept objects of any of Animal's subclasses.

2. Parametric Polymorphism (Compile-time Polymorphism):

Parametric polymorphism, more commonly known in Scala as generics, allows code to be written without commitment to specific data types. The same code works for any type.

For example:

def listOfDuplicates[A](x: A, length: Int): List[A] = {
  if (length < 1) Nil
  else x :: listOfDuplicates(x, length - 1)
}

println(listOfDuplicates(3, 4))  // Outputs: List(3, 3, 3, 3)
println(listOfDuplicates("Scala", 2))  // Outputs: List(Scala, Scala)

The function listOfDuplicates works for any type A.

3. Ad-hoc Polymorphism:

Ad-hoc polymorphism is supported through type classes in Scala. A type class is a type system construct that supports ad-hoc polymorphism.

For example:

trait CanSpeak[A] {
  def speak(a: A): String
}

implicit val intCanSpeak: CanSpeak[Int] = new CanSpeak[Int] {
  def speak(i: Int) = s"Number: $i"
}

implicit val stringCanSpeak: CanSpeak[String] = new CanSpeak[String] {
  def speak(s: String) = s"Word: $s"
}

def echo[A](a: A)(implicit speaker: CanSpeak[A]): String = {
  speaker.speak(a)
}

println(echo(5))  // Outputs: Number: 5
println(echo("Scala"))  // Outputs: Word: Scala

The function echo is polymorphic and can work with any type A as long as there is an implicit instance of CanSpeak[A] available.

Conclusion:

Polymorphism is a cornerstone of the Scala programming language, enabling developers to write more generic, reusable, and expressive code. Whether through subtype polymorphism, generics, or type classes, Scala offers a rich set of tools to harness the power of polymorphism in various contexts.

  1. Polymorphic Functions in Scala:

    Polymorphism allows a function to operate on different types.

    def printType[A](value: A): Unit = {
      println(s"The type of the value is: ${value.getClass.getSimpleName}")
    }
    
    printType(42)      // Int
    printType("Hello") // String
    
  2. Parametric Polymorphism in Scala:

    Parametric polymorphism involves generic types that can work with different data types.

    def identity[A](value: A): A = value
    
    val result: Int = identity(42)
    
  3. Subtype Polymorphism in Scala:

    Subtype polymorphism allows different subtypes to be treated as instances of a common supertype.

    trait Animal {
      def makeSound(): Unit
    }
    
    class Dog extends Animal {
      override def makeSound(): Unit = println("Woof!")
    }
    
    class Cat extends Animal {
      override def makeSound(): Unit = println("Meow!")
    }
    
    def performSound(animal: Animal): Unit = {
      animal.makeSound()
    }
    
    val dog: Animal = new Dog()
    val cat: Animal = new Cat()
    
    performSound(dog) // Woof!
    performSound(cat) // Meow!
    
  4. Type Classes and Polymorphism in Scala:

    Type classes allow ad-hoc polymorphism by defining behaviors for specific types.

    trait Show[A] {
      def show(value: A): String
    }
    
    object ShowInstances {
      implicit val intShow: Show[Int] = (value: Int) => value.toString
      implicit val stringShow: Show[String] = (value: String) => value
    }
    
    def display[A](value: A)(implicit showInstance: Show[A]): Unit = {
      println(showInstance.show(value))
    }
    
    import ShowInstances._
    
    display(42)      // 42
    display("Hello") // Hello
    
  5. Covariant and Contravariant Types in Scala:

    Covariant types preserve the ordering of types, while contravariant types reverse it.

    class Box[+A](value: A)
    
    val box: Box[Any] = new Box[String]("Hello")
    
  6. Polymorphic Methods in Scala:

    Polymorphic methods can operate on different types within a single method definition.

    def printLength[A](value: A): Unit = {
      println(s"Length: ${value.toString.length}")
    }
    
    printLength("Scala") // Length: 5
    printLength(42)      // Length: 2
    
  7. Ad-hoc Polymorphism in Scala:

    Ad-hoc polymorphism is achieved through overloading or type classes to handle different types differently.

    def add(a: Int, b: Int): Int = a + b
    def add(a: String, b: String): String = a + b
    
  8. Examples of Polymorphic Behavior in Scala:

    Polymorphism enables flexible behavior based on the types involved.

    def combine[A](a: A, b: A)(implicit ev: Numeric[A]): A = {
      import ev._
      a + b
    }
    
    val result1: Int = combine(3, 4)      // 7
    val result2: Double = combine(2.5, 3.5) // 6.0