Polymorphism in Scala

Polymorphism is a concept from Object Oriented Programming. As everything else, it is best explained with an example

Subtype polymorphism

Say you want to create a class to represent two types of animals – Cat and Dog. And say you want the class to describe the animal’s color and the sound it makes. To do this, you could create two separate classes, one for Cat and one for Dog as follows:

scala> class Cat (val color:String) {
| val c = color
| def sound() = "meow"
| }
defined class Cat

scala> class Dog (val color:String) {
| val c = color
| def sound() = "Woof"
| }
defined class Dog

scala> val c = new Cat("white")
c: Cat = Cat@36901f79

scala> c.color
res63: String = white

scala> c.sound
res64: String = meow

scala> val d = new Dog("black")
d: Dog = Dog@5cc6f721

scala> d.color
res65: String = black

scala> d.sound
res66: String = Woof

You’ll notice that there is a lot of similarity between the code of Cat and Dog class. In fact, except for the body of ‘sound’ function and name of the classes, everything else is same. Polymorphism allows us to combine common functionality in a single class and create specializations from that class. We could change above example by creating a common class (say Animal) which would contain the common aspects of classes Cats and Dogs. We can then create a Dog or a Cat class by ‘extending’ the Animal class and add features specific to dogs and cats in specialized classes. The following code creates a class of type Animal. The Cat and Dog classes extend Animal and ‘override’ sound function to add specific sounds.

scala> class Animal (color:String) {
| val c = color
| def sound = {println("no sound")}
| }
defined class Animal

scala> class Dog (val color:String) extends Animal (color) {
| override def sound = {println("woof")}
| }
defined class Dog

scala> class Cat (val color:String) extends Animal (color) {
| override def sound = {println("meow")}
| }
defined class Cat

scala> val d = new Dog("black")
d: Dog = Dog@527c4caa

scala> d.sound
woof

scala> d.color
res68: String = black

scala> val c = new Cat("white")
c: Cat = Cat@2f7ea92a

scala> c.sound
meow

scala> c.color
res70: String = white

scala> val a:Animal = new Dog("Brown")
a: Animal = Dog@1e97a037

scala> a.sound
woof

scala> val a2:Animal = new Cat("Grey")
a2: Animal = Cat@24edca5e

scala> a2.sound
meow

In above example, not only were we able to create specialized classes (Cat and Dog) from a generic class (Animal), we could also assign objects of specialized classes to variables of the generic class (val a:Animal = new Dog(“Brown”)). This allows us to create a single interface to entities of different types. We can create functions which accept objects of type Animal but we can pass objects of types which extend Animal. In the following example, we create a class of type ‘myPet’ which takes an argument of type Animal. However, we initialise myPet by passing variables of type Cat and Dog. This is legitimate as Cat and Dog extend Animal and can be used wherever Animal can be used.

scala> class myPet (a:Animal) {
 | def describe = {println("my pet is "+ a.toString +". My pet's color is "+a.c)}
 | }
defined class myPet

scala> val p1 = new myPet(new Dog("brown"))
p1: myPet = myPet@5e0c7244

scala> val p2 = new myPet(new Cat("yellow"))
p2: myPet = myPet@1284c698

scala> p1.describe
my pet is $line226.$read$$iw$$iw$Dog@fc5f2a9. My pet's color is brown

scala> p2.describe
my pet is $line227.$read$$iw$$iw$Cat@410c9cad. My pet's color is yellow

The ability to extend a class to create new classes gives us the flexibility that we do not need to change an existing interface to incorporate new classes. If for example, we create a new class of type Horse, we can pass it to myPet class as long as Horse extends Animal.

scala> class Horse(color:String) extends Animal(color) {
 | override def sound = {println ("nee")}
 | }
defined class Horse

scala> val p3 = new myPet(new Horse("pink"))
p3: myPet = myPet@7d425bf1

scala> p3.describe
my pet is $line235.$read$$iw$$iw$Horse@2f4127da. My pet's color is pink

The above example explains polymorphism type known as subtype polymorphism. Here we create new types by extending a class. The new class (which extends the other class) is called subtype of the other class. The class which gets extended is called superclass of the new class. In our example, Animal is the superclass (because it got extended) of Cat, Dog and Horse. Cat, Dog and Horses are subclasses of Animal (because they extend Animal class).

A subclass ‘inherits’ all the functions and variables of the superclass but not vice versa. Thus in myPet, we could only use those variables and functions which are defined in the Animal class. If a subclass adds new variables in its implementation, then they will become available to the superclass. Thus use of any variable specific to subclass while using a variable type of superclass will create compilation error.

Hint to remember polymorphism: Polymorphism means ‘many shapes’. In above example, the variable ‘a’ of type Animal in myPet class takes many ‘shapes’. It was assigned an instance of a Cat, Dog and later a Horse.

Abstraction

In above example, the class Animal was the base class for other specialized classed like Cat, Dog and Horse. It is probably fair to say that we will not create (instantiate) objects of type Animal as each Animal would be of some specialized type and thus would have its own class. A class which is used to only for the purpose of being used as a superclass and is not expected to be instantiated could be declared as an abstract class. This is done by using the keyword ‘abstract’ in class declaration

scala> abstract class Animal (color:String) {
| def sound
| }
defined class Animal

As the class Animal in example above is declared ‘abstract’,  we cannot create an object of class Animal

scala> val a = new Animal
:12: error: class Animal is abstract; cannot be instantiated
val a = new Animal

Not only do we expect the class Animal to be extended, we can also expect that each class which extends Animal will also create its own ‘sound’ function specific to the animal it represents. Thus we didn’t have to define the implementation of ‘sound’ function. Scala compiler will generate an error if a class extends Animal but doesn’t provide its implementation of ‘sound’ function.

scala> class Dog(color:String) extends Animal(color) {}
:12: error: class Dog needs to be abstract, since method sound in class Animal of type => Unit is not defined
class Dog(color:String) extends Animal(color) {}

scala> class Dog (color:String) extends Animal(color) {
 | override def sound = {println("woof")}
 | }
defined class Dog

Not all methods of an abstract class need to be undefined. If you foresee some methods whose implementation will not change in some of the subclasses that you can implement the body of that method in superclass even it the superclass is abstract.

Hint to remember Abstraction: Abstraction’s dictionary meaning is ‘dealing with ideas, not events’. An abstraction is not fully defined. Thus you should use abstraction where you expect the implementation to be provided later.

Traits

We discussed that the new types Dog, Cat and Horse could be made sub-class of the class Animal (as a Cat, Dog and Horse ‘is a’ Animal). This allowed us to reuse code and also helped us in creating a structure for our design. What if there are other commonalities between Cats, Dogs and Horse but we cannot put them in Animal class because the other common features would not fit in Animal class. Say we want to say that  Dogs and Animals can also be trained to do tricks. As not all Animals can be trained, we do not want to add this code in Animal class. We could create another class, say Tricks, but the problem is that Scala doesn’t allow a class to extend from multiple  classes. You cannot do following

scala> class Trick (tricks:List[String]) {
| def printTricks = {println(tricks) }
| }
defined class Trick

scala> //following will not compile
scala> class Dog (color:String, tricks:List[String]) extends Animal (color) extends Trick (tricks)

To use the features from Trick, you’ll have to declare it as a Trait and use ‘with’ to add it to Dog class. See following:

scala> trait Trick {
| def printTricks
| }
defined trait Trick


scala> class Dog (color:String, tricks:List[String]) extends Animal (color) with Trick {
| def sound = println ("woof")
| def printTricks = println(tricks)
| }
defined class Dog

scala> val d = new Dog ("white", List("jump", "sit"))
d: Dog = Dog@51d9b06c

scala> d.sound
woof

scala> d.printTricks
List(jump, sit)

Implicit

To understand Adhoc polymorphism, a good understanding of ‘implicit’ is required. The keyword ‘implicit’ indicates to Scala compiler to look for ways to convert one type to another. For example, following function prints a String in REPL

scala> def functionForString(a:String) = {println("string is "+a)}
functionForString: (a: String)Unit

To call this function, we have to pass an argument of type String. Thus the following usage is not valid (as we pass an argument of type Int)

scala> functionForString(1)
:13: error: type mismatch;
found : Int(1)
required: String
functionForString(1)

We could make the above function call work if we could give some mechanism to Scala compiler to convert an Int to a String. If such a conversion is available (in scope), then Scala compiler would use that conversion and call to functionForString would work. The conversion could be provided by following way:

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> implicit def intToString(a:Int) = {a.toString}
intToString: (a: Int)String

scala> functionForString(1)
string is 1

Note that a.toString is already available in Scala library which could convert int to String but compiler will not use it because the definition of toString is not declared as implicit.

Scala uses the implicit conversions to convert from one type to another. In case, Scala compiler finds multiple ways to convert from one type to another then it will generate an error complaining about ambiguity. See the following example in which we provide another function which could convert an integer into a string. As Scala sees both our conversion functions, it doesn’t know which one to use and thus generates error

scala> implicit def intToString2(a:Int) = {"hello"}
intToString2: (a: Int)String

scala> functionForString(2)
:18: error: type mismatch;
 found : Int(2)
 required: String
Note that implicit conversions are not applicable because they are ambiguous:
 both method intToString of type (a: Int)String
 and method intToString2 of type (a: Int)String
 are possible conversion functions from Int(2) to String
 functionForString(2)

In addition to functions, ‘implicit’ can be used with classes as well. Following class implicitly converts an integer into a variable of type Dog

scala> implicit class Dog(a:Int) {
 | def sound = {"woof"}
 | }
defined class Dog

scala> def soundofDog(d:Dog)= {d.sound}
soundofDog: (d: Dog)String

scala> soundofDog(1)
res2: String = woof

The above code converts Int to Dog because of the way constructor of classes work in Scala. In Scala, any code in the body of the class which is not inside a function, including arguments to the class, is used as a primary constructor of the class. Thus, when we write Dog(a:Int), Scala compiler would automatically create a primary constructor for class Dog. The primary constructor would take Int argument and would construct a Dog object. In essence, it takes Int as an input and returns Dog as an output.

‘implicit’ can be used with variables, vals, defs and classes. When used with vars or vals, scala compiler would use their values wherever it finds it appropriate to allow the code to compile

scala> implicit val si = "implicit hello"
si: String = implicit hello

//implicit tells compiler that if the argument s (which is declared as implicit
//is missing the compiler may look at available implicit values in scope and
// use those values instead. Thus if we do not pass a string to helloFunction
//compiler will use si (an implicit value of type string available in scope
scala> def helloFunction(implicit s:String) = {println(s)}
helloFunction: (implicit s: String)Unit

scala> helloFunction("hello without implicit")
hello without implicit

 //we call helloFunction without explicitly passing string argument.
//scala compiler will look for implicit definitions available in scope and will use 
//them so that helloFunction compiles
scala> helloFunction 
implicit hello

When used with def or class, implicit converts the argument (input of a function or a class) into the result (return type of the function or the object of the class).

Parameters declared as implicit should be at the end of the function parameter list.

scala> implicit val implicitOneString = "one"
implicitOneString: String = one

//implicit argument list at the end of function's arguments

scala> def randomFunction(s1:String)(implicit s2:String) { println(s1+" "+s2)}
randomFunction: (s1: String)(implicit s2: String)Unit

scala> randomFunction("two")
two one

//this doesnt compile as implicit list should be at the end

scala> def randomFunction (implicit s2:String)(s1:String) { println(s1+" "+s2)}
<console>:1: error: '=' expected but '(' found.
def randomFunction (implicit s2:String)(s1:String) { println(s1+" "+s2)}
^

//example of multipe implicit and non implicit arguments. Implicit list is always at the end

scala> def randomFunction(s1:String, s2:String)(implicit s3:String, s4:String) { println(s1+" "+s2+" "+s3+" "+s4)}
randomFunction: (s1: String, s2: String)(implicit s3: String, implicit s4: String)Unit


//we do not supply two implicit arguments. Scala compiler substitutes them with available implicit strings

scala> randomFunction("two","three") 
two three one one

scala> implicit def twoIntsToString(x:Int, y:Int) = {x.toString+" "+y.toString}
twoIntsToString: (x: Int, y: Int)String

scala> randomFunction("two","three")(1,2)
two three 1 2

Implicit values are used within some context. For example, if you have a constant (say gravitational constant ‘g’ =9.8) which you’ll use repeatedly (say in equations like F=mg and pendulum equation 2*pi*sqrt(l/g)), then it might be easier to define ‘g’ as implicit and create the equations as follows

scala> def force(m:Double)( implicit g:Double) = m*g
force: (m: Double)(implicit g: Double)Double

scala> implicit val g = 9.8
g: Double = 9.8

scala> import java.math._
import java.math._

scala> def pendulum (l:Int)(implicit g:Double) = 2*math.Pi*math.sqrt(l/g)
pendulum: (l: Int)(implicit g: Double)Double

scala> val f=force(11)
f: Double = 107.80000000000001

scala> val p = pendulum(10)
p: Double = 6.346975625940523

Ad-hoc polymorphism

We discussed ‘implicit’ above so that we could understand Ad-hoc polymorphism. By using ‘implicit’ we can convert from one data type to another. This functionality becomes useful when we want to create a function (or a class), say ‘f(a:A)’ which can work with multiple data types (‘A’ represents that ‘f’ can work with any data type). Each  data type will have its own specific implementation. Note that we are not discussing overloading. If we want ‘f’ to work with Integer or String arguments, we will not write f(a:Int) {some int implementation} and f(a:String) {some string specific implementation}. ‘f’ will have a single implementation. The single implementation of ‘f’ will call another function ‘f1’which will have different implicit implementations for String and Integer. The compiler will decide which implementation to call based on the type of ‘A’. This pattern is better than overloading because overloading would work only if we have the source code of ‘f’ (and thus we can write a new implementation of ‘f’ for a new type, say Double). Using implicit approach, the signature of ‘f’ is not affected (and thus we do not need the source code of ‘f’). We will create a new implicit implementation of ‘f1’ which would work for Double. The compiler would then use different ‘f1’ depending on whether ‘A’ is a String or Integer or a Double.

Let us see this in action. Let us say we want to create a generic class which could describe any object. By ‘describe’, I mean, print a short line about the object. For example, if we pass an object of type Dog then  it prints Dog’s color and sound, if we pass an Integer, then it prints integer’s value. If we have the source code for Dog or Integer then we could create a trait as follows and implement the trait in these classes. But let us assume we do not have access to the implementation of Dog or Integer classes (we actually do not have access to Integer class!).

scala> trait DescribeObject {
| def myDescription:String
| }
defined trait DescribeObject

scala> class Dog (color:String) extends DescribeObject {
| def sound = "woof"
| def myDescription = "My color is "+color + "and I say "+sound
| }
defined class Dog

scala> val d = new Dog("brown")
d: Dog = Dog@68dcfd52

scala> d.myDescription
res11: String = My color is brownand I say woof

//we could have written the same for Integer but sadly we do not have access to Integer's implementation

Ad-hoc polymorphism would allow us to add ‘myDescription’ functionality in any class using implicit (even if we do not have the source code of that class). Thus we can add new functionality in code retrospectively.

We could implement ad-hoc polymorphism in two ways (a) implicit conversion (b) type classes

Ad-hoc polymorphism using Implicit conversion (view bound or implicit conversion)

Using implicit conversion, we can create our generic class as follows (this code will not compile yet)

class DescribeObject[A](a:A) {
| def describe (implicit ev:A=>DescribeObjectTrait[A]) = {println(a.myDescription)}
| } 
defined class DescribeObject

The above code creates a generic class (denoted by [A]) whose ‘describe’ function takes an implicit parameter ‘ev’, Similar to helloFunction(implicit s:String) (discussed earlier), this declaration tells compiler it should look for ways to convert ‘A’ to DesribeObjectTrait[A] (denoted by A=>DescribeObjectTrait[A])’. ‘a’ would not have the implementation of ‘myDescription’ but once compiler finds a way to convert A (data type of ‘a’) to DescribeObjectTrait[A], then the code will work because DesribeObjectTrait[A] has the implementation of myDescription.

Note that as we have used [A] while declaring class (DescribeObject[A]), we do not need to use [A] while declaring ‘describe’ i.e. we do not write describe[A].

We will need to follow these steps to use ad-hoc polymorphism using implicit conversion

  • In the above example, we are calling ‘myDescription’ on ‘a’ but we do not know that ‘a’has this function. Thus each type which we want to be able to pass to our class should have an implementation of myDescription function. To do this, we will create a Trait with function myDescription
scala> trait DescribeObjectTrait[A] {
 | def myDescription:String
 | }
defined trait DescribeObjectTrait

Notice the use of the letter ‘A’ in creating the trait. It signifies that the trait is ‘type class’. A type class defines a behaviour (in our case, myDescription). Any class which could implement this behaviour (directly or indirectly) can be considered to be a member of the type class.

  • Next, we will implement the trait for each type we want to support.
//we are making a class which takes 'int' and creates a class of type DescribeInteger
// which is a base class of DescribeObjectTrait[Int] and thus can be considered to
//be of type DesribeObjectTrait[Int]. So this class could give us the conversion 
// from int to DescribeObjectTrait[Int]

scala> class DescribeInteger (i:Int) extends DescribeObjectTrait[Int] {
| def myDescription:String = {"I am an integer. My value is "+i.toString}
| }
defined object DescribeInteger

scala> class Dog (color:String) {
| val c = color
| def sound = "woof"
| }
defined class Dog

//this class gives conversion from Dog to DescribeObjectTrait[Dog]
scala> class DescribeDog (d:Dog) extends DescribeObjectTrait[Dog] {
| def myDescription:String = {"I am a Dog. My color is "+d.c+" and I say "+d.sound}
| }
defined object DescribeDog
  • We provide implicit conversions. These conversions would take an input the type we want to support and return the corresponding type class which has the type specific implementation of trait.
//The previous step provided the conversion but compiler will not use it till we
//explicitly tell it to use it using implicit

implicit def toDescribeInt(i:Int) = new DescribeInteger(i)

implicit def toDescribeDog(d:Dog) = new DescribeDog(d)

//obviously, we can always directly provide the the conversion because compiler
//will start looking for implicit definitions only when a parameter is missing

val f = (i:Int) => new DescribeInteger(i)
//we pass 'f' directly
new DescribeObject(1).describe(f)
I am an integer. My value is 1
res0: Unit = ()
  • Now we create our generic class. The declaration tells compiler that there is a conversion available from A to DescribeObjectTrait[A]. So even if ‘a’ hasn’t got myDescription, do not raise error. Compiler assumes (or probably can check by looking at DescribeObjectTrait definition) that DescribeObjectTrait[A] has got myDescription
class DescribeObject[A](a:A) {
def describe (implicit ev:A=>DescribeObjectTrait[A]) = {println(a.myDescription)}
} defined class DescribeObject


  • Now this works
scala> scala> new DescribeObject(1).describe
I am an integer. My value is 1

scala> new DescribeObject(new Dog("brown")).describe
I am a Dog. my color is brown and I say woof

In summary, a.myDescription worked for a=Integer and a=Dog even though neither Integer nor Dog had this function defined.

To understand how above ‘implicit’ works, scala wants to call ‘myDescription’ on Integer or Dog types. As it cannot find it, it notices that it has access to definitions (in scope) to which it can pass the types Integer and Dog (toDescribeInt and toDescribeDog) and the new types (DescribeInteger and DescribeDog) can call myDescprition.

Scala used implicit conversion because we told the compiler that when the function ‘describe’ will be used for a type ‘A’, there will be an implicit conversion (called ‘ev’) available from type A to DescribeObjectTrait[A]. As DescribeInteger and DescribeDog extends DescribeObjectTrait, these classes can be used instead of DescribeObjectTrait (a subclass can be used in place of superclass)

Instead of creating a generic class, we could also have created a generic function. It is done in same way as the generic class as follows:

scala> def describe[A](a:A)(implicit ev:A=>DescribeObjectTrait[A]) = {
println(a.myDescription)
}
describe: [A](a: A)(implicit ev: A => DescribeObjectTrait[A])Unit

scala> describe(1)
I am an integer. My value is 1

scala> describe(new Dog("white"))
I am a Dog. my color is white and I say woof

Note that we used type parameter, A, when we declared describe (describe[A]). It is similar to when we created the generic class (DescribeObject[A]). The only difference is that when we declared ‘describe’ inside the DescribeObject class, we didn’t type parameterise the describe function then.

A syntax sugar which could be used for implicit conversion is called view bounds. It shortens the syntax for functions using implicit conversion. Symbol <% is used to denote a view bound. It is telling the compiler to ‘view’ type A as type DescribeObjectTrait[A]. Thus the above describe function could also be written as follows:

//use A<%Trait instead of 'implicit ev:A=>Trait'
scala> def describe2[A <% DescribeObjectTrait[A]](a:A) = {println(a.myDescription)}
describe2: [A](a: A)(implicit evidence$1: A => DescribeObjectTrait[A])Unit

scala> describe2(1)
I am an integer. My value is 1

scala> describe2(new Dog("brown"))
I am a Dog. My color is brown and I say woof

View bounds are discussed further in Type classes and Bounds blog.

Ad-hoc polymorphism using type classes (context bound or implicit parameters)

In the previous section, when using implicit conversion, we did the implicit conversion of functions toDescribeInt and toDescribeDog to DescribeInteger and DescribeDog classes.

implicit def toDescribeInt(i:Int) = new DescribeInteger(i)

implicit def toDescribeDog(d:Dog) = new DescribeDog(d)

In type class pattern, we will make DescribeInteger and DescribeDog themselves implicit variables (singleton object), thus we call them parameters and not conversions. The rest of the sequence stays the same (with small change in way we define implicit in ‘describe’ function as explained below

  • We start with usual trait definition. Here, the functions of the trait has to take arguments of the type. The reason for this is explained later
scala> trait DescribeObjectTrait[A] {
 | def myDescription(a:A):String //function has to take arguments of the type
 | }
defined trait DescribeObjectTrait
  • We will implement the trait for each type we want to support but this time we will simultaneously create an object of the class implementing the trait as implicit. As we cannot pass arguments when creating the object this way, we mentioned earlier that the functions of the trait have to take arguments.
//this will not compile.Cannot pass argument
scala> implicit object DescribeInteger(i:Int) extends DescribeObjectTrait[Int]

//this is ok
scala> implicit object DescribeInteger extends DescribeObjectTrait[Int] {
 | def myDescription (i:Int) = {"I am int with value "+i.toString}
 | }
defined object DescribeInteger
  • Now we DO NOT need to make this implicit declaration
// implicit def toDescribeInt(i:Int) = new DescribeInteger(i)

// implicit def toDescribeDog(d:Dog) = new DescribeDog(d)
  • Now we create our generic function and use it
scala> def describe[A](a:A)(implicit ev:DescribeObjectTrait[A]) = {println(ev.myDescription(a))}
describe: [A](a: A)(implicit ev: DescribeObjectTrait[A])Unit

scala> describe(1)
I am int with value 1

//as with implicit conversion, we can provide the 'ev' argument directly.
class DescribeInteger extends DescribeObjectTrait[Int] {
  def myDescription (i:Int) = {"I am int with value "+i.toString}
}

val io = new DescribeInteger

describe(1)(io)
I am int with value 1
res0: Unit = ()

The above statement tells the scala compiler to look for an implicit value (parameter, call it ‘ev’) which converts from type A (Int) to type DescribeObjectTrait[A] (DescribeObjectTrait[Int]). The implicit value was created when we declared implicit DescribeInteger object.

Note the use of ‘ev.myDescription’ in describe function in implicit parameter compared to use of ‘a.myDescription’.

A syntax sugar which could be used for implicit value is called context bounds. It shortens the syntax for functions using implicit value. Symbol : is used to denote a view bound. Thus the above describe function could also be written as follows:

scala> def describe[A:DescribeObjectTrait](a:A) {
 | println(implicitly[DescribeObjectTrait[A]].myDescription(a))
 | }
describe: [A](a: A)(implicit evidence$1: DescribeObjectTrait[A])Unit

scala> describe(1)
I am an integer with value 1

We use ‘implicitly’ function to access the instance of DescribeObjectTrait[A] as we do no longer have ‘ev’ variable available.

Obviously, we can provide an object of type DescribeObjectTrait directly to describe the function.

//create a val of type DescribeObjectTrait[Int]
scala> implicit val DescribeIntegerVal = new DescribeObjectTrait[Int] {
| def myDescription (i:Int) = {"I am also an int with value "+i.toString}
| }
DescribeIntegerVal: DescribeObjectTrait[Int] = $anon$1@64a8c844

//provide the value directly instead of letting compiler look for suitable implicit value
scala> describe(1)(DescribeIntegerVal)
I am also an int with value 1

//the compiler uses the object instead of val. This has to do with precedence of how scala compiler deals with implicits if multiple options are available
scala> describe(1)
I am int with value 1

If we review the implicit conversion and type class patterns, type class pattern looks more straight forward. It is very similar to the way we use functions and arguments in general. We have a function ‘f’ (describe) which needs an argument (ev) of some type (DescribeObjectTrait[A]). We define a value (or object) of type DescribeObjectTrait[A] and the function uses it (directly or implicitly). The implicit conversion pattern looks a bit convoluted as it requires an extra step of creating the implicit mapping between a function (toDescribeInt(i:Int)) and a class implementing the trait (DescribeInteger(i)). Type class pattern is more readable than implicit conversion. The implicit conversion allows us to create classes which can accept arguments which might be preferred (or convenient) approach in some cases.

Comparing the two different approaches to implement ad-hoc polymorphism, the type class pattern is more intuitive. ev.myDesription(a) is more intuitive because we know that ‘ev’ has a function myDescription which takes ‘a’. On the other hand, a.myDescription (used in conversion type ad-hoc polymorphism) could be confusing as ‘a’ hasn’t got myDescription function

Parametric Polymorphism

We can use another type of polymorphism in Scala called Parametric Polymorphism.

Let us look at List collection to understand parametric polymorphism. A List is a collection of values of some data type. List in itself is not a data type. It is the combination of List and the type of data it holds that defines a List

scala> val l = List (12,3)
l: List[Int] = List(12, 3)

scala> val c = List('a','e')
c: List[Char] = List(a, e)

scala> val s = List("a","e")
s: List[String] = List(a, e)

Let us say we want to define a function which can take a list (irrespective of the type of list – integer, string, character) and returns the head of the list. Such a function need to be generic so that it can accept a list of any type. We discussed above using letters like ‘A’ to denote generalization based on types. We can use the same approach to create our generic function

scala> def myListHead[A](a:List[A]) = {a.head}
myListHead: [A](a: List[A])A

scala> val l = List (12,3)
l: List[Int] = List(12, 3)

scala> myListHead(l) //our function works with List[Int]
res2: Int = 12

scala> myListHead(c) //our function works with List[Char]
res3: Char = a

scala> myListHead(s)//our function works with List[String]
res4: String = a

Notice the use of [A] to denote that the function can argument of various data types.

Each type of polymorphism discussed in this blog works in different ways. In subtype polymorphism, a function (from base class) could have different implementations in different sub classes (many forms/implementations of a function (overriding)). In adhoc and parametric polymorphism, the same function works for different types (many forms of types). It is worthy to mention that in subtype polymorphism, the decision of which function to use is made at compile time. In subtype, the decision of which function to call is made at

It is worthy to mention the difference between parametric polymorphism and Ad-hoc polymorphism  which is that parametric polymorphism has the same implementation for all types. In myListHead example, the same function is used for all types ‘A’. In ad-hoc polymorphism, we created a specific implementation for each type (there was a type specific implicit implementation for each type).

It is also worthy to mention that in subtype polymorphism, the decision of which function to use is made at compile time. In subtype, the decision of which function to call is made at runtime (depending on which type of subclass object is assigned to the base class variable). In parametric, as the same function is used for all types, there is no decision required.

Prebuilt implicit values in Scala

The Scala library comes with many implicit values (of type traits like Numeric, Ordering) which are used extensively by methods in collections.

Trait Numeric

Collections are supposed to be generic as they can hold data of any type. However, not all methods of a collection are logical for all data types. One such example is ‘product’ method in List class. It is used to multiple elements of a list. Obviously, multiplication makes sense for types Integers, Float etc. but not for types String, Boolean or user defined types (classes) like Car, Animal etc. Thus even though, ‘product’ is available in List collection, it can only be used on types which are similar in some way (eg, they should be numbers). To represent such similarity, Scala library provides some traits which similar types should implement. The methods like ‘product’ (which are applicable only for some similar types) use the methods of the common trait to implement their own functionality. For example, all types which can be used as numbers can implement Numeric trait which defines following abstract methods:

abstract def compare(x: T, y: T): Int

abstract def fromInt(x: Int): T

abstract def minus(x: T, y: T): T

abstract def negate(x: T): T

abstract def plus(x: T, y: T): T

abstract def times(x: T, y: T): T

abstract def toDouble(x: T): Double

abstract def toFloat(x: T): Float

abstract def toInt(x: T): Int

abstract def toLong(x: T): Long

All the methods would allow a type to behave like a number. It could be compared, added(plus), subtracted (minus), multiplied(times), negated (negate) etc. Thus if a type can implement above trait, it can be treated as a number

If we define the product method using methods of the Numeric trait, then the type which implements Numeric can be used with method ‘product’.

The method is defined and implemented as follows in Scala:

def product[B >: A](implicit num: Numeric[B]): B = foldLeft(num.one)(num.times)

As you notice, the method ‘product’ uses methods of ‘num (implicit value of type Numeric) to find the product (see List to understand foldLeft). ‘one’ is already defined in Numeric as

def one = fromInt(1)

Thus, as long as a type can provide the implementation of ‘times’ (i.e. implements Numeric), it can be used with method ‘product’. Whether the result of the product makes sense is a separate discussion. See the following example:

//product works for Double
val l1 = List(1.1, 2.2, 3.3)
l1: List[Double] = List(1.1, 2.2, 3.3)

l1.product
res0: Double = 7.986000000000001

//product works for Int
val l2 = List (1,2,3,4,5)
l2: List[Int] = List(1, 2, 3, 4, 5)

l2.product
res1: Int = 120

//product doesn't work for String
val l3 = List("1","2","3","4")
Error:(31, 5) could not find implicit value for parameter num: Numeric[scala.collection.immutable.Vector[Int]]
l1.product
 ^

//but if we make String implement Numeric, then we can use product

implicit object MyString extends Numeric[String] {

  //dummy implmentations for some methods as they do not make sense for strings.
  //compare could be used in STring but I am feeling lazy to implement it
 def compare(x: String, y: String): Int = { 1}
 def fromInt(x: Int): String = {x.toString}
 def minus(x: String, y: String): String = x
 def negate(x: String): String = "-"+x
 def plus(x: String, y: String): String = x+y

//this is the method of interest to us
 def times(x: String, y: String): String = x+y

 def toDouble(x: String): Double = 1.0
 def toFloat(x: String): Float = 1.0f
 def toInt(x: String): Int = 1
 def toLong(x: String): Long = 1L

}

val l3 = List("1","2","3","4")
l3.product
res2: String = 11234

To understand how the above code works for type String, the method ‘product’ by its definition is looking for an implicit value of type Numeric[String]. We provide this value by declaring an object of type MyString. Inside MyString, we have provided the implementation of ‘times’ method which ‘product’ uses to find the product. As ‘one’ is defined as Integer value 1 in Numeric class, we will always get 1 in the beginning.

val l3 = List("2","2","3","4")
l3.product
res2: String = 12234

We could remove 1 by overriding ‘one’ method in our MyString object.

implicit object MyString extends Numeric[String] {

  //dummy implmentations for some methods as they do not make sense for strings.
  //compare could be used in STring but I am feeling lazy to implement it
  def compare(x: String, y: String): Int = { 1}
  def fromInt(x: Int): String = {x.toString}
  def minus(x: String, y: String): String = x
  def negate(x: String): String = "-"+x
  def plus(x: String, y: String): String = x+y
  def times(x: String, y: String): String = x+y
  def toDouble(x: String): Double = 1.0
  def toFloat(x: String): Float  = 1.0f
  def toInt(x: String): Int = 1
  def toLong(x: String): Long = 1L

//override one so that it returns empty string
  override def one:String = ""

}

val l3 = List("2","2","3","4")
l3.product
res2: String = 2234

In case you are wondering, for types, Int, BigInt, Float, Double, scala.math.Numeric provides the implicit values of types Numeric[Int], Numeric[BigInt] etc. Search source code  of Numeric.scala for keyword ‘extends Numeric[Double]

Note on understanding scala documentation and finding soure code

If you read scala documentation, you might find that the syntax in documentation is

def product: A

[use case]
The words [use case] signifies that use could use ‘product’ method without the second argument ‘num’ because the second argument ‘num’ is provided automatically (implicitly).
If you look at the scaladoc (online documentation of Scala library, simply google it) and open the documentation of product (in List class), you can see that it says “Definition Classes”. There you can see that it is defined by GenTraversableOnce and implemented by TraversableOnce. If you click through to the scaladoc of TraversableOnce, you find a link straight to the correct source file (“Source”, under the summary of the class).
scaladoc
Trait Ordering
You’ll often find a need to provide ability to compare objects of a class with each other to find which is greater, smaller or equal to the other. Many methods in Scala’s Collection library also require that objects in these collection are comparable. One such method is SortBy in List class.
Try the following example. We have a class Person. We have a list of Person objects. We want to sort these objects based on their name, age or birth date.
import java.util.Date
class Person (s:String, i:Int, d:Date) {
  val name = s
  override def toString = name
  val age = i
  val dob = d
}

val a1 = new Person("Andy",11, new Date())
val a2 = new Person("ann",12,new Date())
val a3 = new Person("Beth",31, new Date())
val a4 = new Person("Danny",10, new Date())
val a5 = new Person("peter",1, new Date())

val la = List(a1,a2,a3,a4, a5)

la.sortBy(x=>x.name)
res0: List[Person] = List(Andy, Beth, Danny, ann, peter)

la.sortBy(x=>x.age)
res1: List[Person] = List(peter, Danny, Andy, ann, Beth)

la.sortBy(x=>x.dob)
res2: List[Person] = List(Andy, ann, Beth, Danny, peter)
//this will not work though
la.sortBy(x=>x)
Error:(27, 11) No implicit Ordering defined for A$A120.this.Person.
la.sortBy(x=>x)
 ^
Following is the syntax of sortBy
def sortBy[B](f: (A) ⇒ B)(implicit ord: math.Ordering[B]): List[A]
sortBy expects that object of type B should be comparable with each other (greater, smaller, equal). We can add the ability compare objects of type B by providing an implicit value of type Ordering[B]. This is the reason la.sortBy(x=>x) didn’t work because there is no implicit value of type Ordering[Person]. We didn’t have this problem with x=>x.name or x=>x.age because name (String) and age (Int) already have an implicit value of type Ordering[String] and Ordering [Int] in scope.
We could create an implicit value of type Ordering[Person] as follows
implicit object myPersonOrdering extends Ordering[Person] {
  override def compare(x: Person, y: Person) = if (x.name.compareTo(y.name) == 0) {
    if (x.age.compareTo(y.age) == 0) {
      x.dob.compareTo(y.dob)
    } else (x.age.compareTo(x.age))
  } else (x.name.compareTo(y.name))
}

In above code, we have provided the logic for how to compare two Person objects. Their names will be compared first. If names are same, then their age will be compared. If their age is also same, then their date of birth will be compared. Note that we could have used any other logic to compare two person objects.

la.sortBy(x=>x)
res3: List[Person] = List(Andy, Beth, Danny, ann, peter)

sortBy is defined in SeqLike.scala. It calls method sorted which in turn calls Java’s method

4 thoughts on “Polymorphism in Scala

Leave a comment