Scala Tutorial
Basics
Control Statements
OOP Concepts
Parameterized - Type
Exceptions
Scala Annotation
Methods
String
Scala Packages
Scala Trait
Collections
Scala Options
Miscellaneous Topics
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.
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.
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
.
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.
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.
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
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)
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!
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
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")
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
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
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