Polymorphism in Scala - I
Scala, as a functional and an object-oriented language, can implement both subtype and generic polymorphism. The first one is usually associated with OO languages, and the second, with functional.
First things first, polymorphism is when a function can handle arguments of many types. For instance, some languages make it possible to write things like "a" + "b" == "ab"
and 1 + 2 == 3
. The +
function can operate on different types of data (strings and integers). Another place where we can identify polymorphism is when a type can have instances of many types. It sounds weird, but it is actually simple. For example, in Scala we could do:
abstract class Animal {
// some methods
}
class Dog extends Animal {
// implementation
}
class Cat extends Animal {
// implementation
}
In the above example, a type Animal
can have instances of two types: a new Dog()
of type Dog
and a new Cat()
of type Cat
.
This example, of extending a class, is what is called subtyping. The first example is called generic polymorphism. Both can be observed in the implementation of a immutable linked-list.
trait List[T] {
def isEmpty: Boolean
def head: T
def tail: List[T]
}
The trait
in Scala can be seen as an interface
in Java (although it is not always the case: a trait is compiled to a Java interface only if it has abstract members; otherwise, it has no correspondence in Java code).
Traits and classes can be parameterized — they can accept a type as argument. The List[T]
trait accepts a generic type T
.
Then, the implementation:
class Nil[T] extends List[T] {
def isEmpty: Boolean = true
def head: Nothing = throw new NoSuchElementException
def tail: Nothing = throw new NoSuchElementException
}
class Cons[T](val head: T, val tail: List[T]) extends List[T] {
def isEmpty: Boolean = false
}
The example shows subtyping (instances of List[T]
can be of type Cons
or Nil
) and generics (a List
can handle arguments of any type). A method to create a list could be written like this:
def singleton[T](element: T) = new Cons(element, new Nil)
Calling that method could be done like this:
singleton(1)
// Cons[Int] = Cons(1, Nil)
singleton("a")
// Cons[String] = Cons("a", Nil)
When calling singleton
, we could pass in the type (singleton[Int](1)
), but it is not necessary because the Scala compiler can infer it.
Abstract class vs trait
Deciding between an abstract class or a trait is not difficult: use trait whenever possible.
Although an abstract class has more pros than traits, the recommendation — that comes from the book Programming in Scala, by Prof. Martin Odersky — is to favor trait because, if necessary, it is easier to turn it into an abstract class later, and because it is possible that a subtype can extend multiple traits.
trait X {
// ...
}
trait Y {
// ...
}
class Foo extends X with Y {
// ...
}
The situations in which an abstract class would be preferred would be when:
- Performance is important. The invocation of a class is faster at runtime.
- We need constructor parameters.
-
A Java class will inherit from it.
- Second part: Polymorphism in Scala — II