Many software engineers may not need to explicitly deal with type parameterization or generic types in their day-to-day job, but it’s very likely that the libraries and frameworks that they’re heavily using have already done their duty to ensure static type-safety via such parametric polymorphism feature.
In a static-typing functional programming language like Scala, such feature would often need to be used first-hand in order to create useful functions that ensure type-safety while keeping the code lean and versatile. Generics is apparently taken seriously in Scala’s inherent language design. That, coupled with Scala’s implicit conversion, constitutes a signature feature of Scala’s. Given Scala’s love of “smileys”, a few of them are designated for the relevant functionalities.
Merge Sort
Merge Sort is a popular text-book sorting algorithm that I think also serves a great brain-teasing programming exercise. I have an old blog post about implementing Merge Sort using Java Generics. In this post, I’m going to use Merge Sort again to illustrate Scala’s type parameterization.
By means of a merge function which recursively merge-sorts the left and right halves of a partitioned list, a basic Merge Sort function for integer sorting might be something similar to the following:
def mergeSort(ls: List[Int]): List[Int] = {
def merge(l: List[Int], r: List[Int]): List[Int] = (l, r) match {
case (Nil, _) => r
case (_, Nil) => l
case (lHead :: lTail, rHead :: rTail) =>
if (lHead < rHead)
lHead :: merge(lTail, r)
else
rHead :: merge(l, rTail)
}
val n = ls.length / 2
if (n == 0)
ls
else {
val (a, b) = ls splitAt n
merge(mergeSort(a), mergeSort(b))
}
}
A quick test …
scala> val li = List(9, 5, 16, 3, 4, 11, 8, 12) li: List[Int] = List(9, 5, 16, 3, 4, 11, 8, 12) scala> mergeSort(li) res1: List[Int] = List(3, 4, 5, 8, 9, 11, 12, 16)
Contrary to Java Generics’ MyClass<T> notation, Scala’s generic types are in the form of MyClass[T]. Let’s generalize the integer Merge Sort as follows:
def mergeSort[T](ls: List[T]): List[T] = {
def merge(l: List[T], r: List[T]): List[T] = (l, r) match {
case (Nil, _) => r
case (_, Nil) => l
case (lHead :: lTail, rHead :: rTail) =>
if (lHead < rHead)
lHead :: merge(lTail, r)
else
rHead :: merge(l, rTail)
}
val n = ls.length / 2
if (n == 0)
ls
else {
val (a, b) = ls splitAt n
merge(mergeSort(a), mergeSort(b))
}
}
:15: error: value < is not a member of type parameter T
if (lHead < rHead)
^
The compiler immediately complains about the ‘<‘ comparison, since T might not be a type that supports ordering for ‘<‘ to make any sense. To generalize the Merge Sort function for any list type that supports ordering, we can supply a parameter in a curried form as follows:
// Generic Merge Sort using math.Ordering
import math.Ordering
def mergeSort[T](ls: List[T])(order: Ordering[T]): List[T] = {
def merge(l: List[T], r: List[T]): List[T] = (l, r) match {
case (Nil, _) => r
case (_, Nil) => l
case (lHead :: lTail, rHead :: rTail) =>
if (order.lt(lHead, rHead))
lHead :: merge(lTail, r)
else
rHead :: merge(l, rTail)
}
val n = ls.length / 2
if (n == 0)
ls
else {
val (a, b) = ls splitAt n
merge(mergeSort(a)(order), mergeSort(b)(order))
}
}
Another quick test …
scala> val li = List(9, 5, 16, 3, 4, 11, 8, 12)
li: List[Int] = List(9, 5, 16, 3, 4, 11, 8, 12)
scala> mergeSort(li)(Ordering[Int])
res2: List[Int] = List(3, 4, 5, 8, 9, 11, 12, 16)
scala> val ls = List("banana", "pear", "orange", "grape", "apple", "strawberry", "guava", "peach")
ls: List[String] = List(banana, pear, orange, grape, apple, strawberry, guava, peach)
scala> mergeSort(ls)(Ordering[String])
res3: List[String] = List(apple, banana, grape, guava, orange, peach, pear, strawberry)
That works well, but it’s cumbersome that one needs to supply the corresponding Ordering[T] for the list type. That’s where implicit parameter can help:
// Generic Merge Sort using implicit math.Ordering parameter
import math.Ordering
def mergeSort[T](ls: List[T])(implicit order: Ordering[T]): List[T] = {
def merge(l: List[T], r: List[T]): List[T] = (l, r) match {
case (Nil, _) => r
case (_, Nil) => l
case (lHead :: lTail, rHead :: rTail) =>
if (order.lt(lHead, rHead))
lHead :: merge(lTail, r)
else
rHead :: merge(l, rTail)
}
val n = ls.length / 2
if (n == 0)
ls
else {
val (a, b) = ls splitAt n
merge(mergeSort(a), mergeSort(b))
}
}
Testing again …
scala> mergeSort(li) res3: List[Int] = List(3, 4, 5, 8, 9, 11, 12, 16) scala> mergeSort(ls) res4: List[String] = List(apple, banana, grape, guava, orange, peach, pear, strawberry)
Note that the ‘if (lHead < rHead)’ condition is now replaced with ‘if (order.lt(lHead, rHead))’. That’s because math.Ordering defines its own less-than method for generic types. Let’s dig a little deeper into how it works. Scala’s math.Ordering extends Java’s Comparator interface and implements method compare(x: T, y: T) for all the common types, Int, Long, Float, Double, String, etc. It then provides all these lt(x: T, y: T), gt(x: T, y: T), …, methods that know how to perform all the less-than, greater-than comparisons for various types.
The following are highlights of math.Ordering’s partial source code:
// Scala's math.Ordering source code highlight
import java.util.Comparator
...
trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable {
...
def compare(x: T, y: T): Int
...
override def lt(x: T, y: T): Boolean = compare(x, y) < 0
override def gt(x: T, y: T): Boolean = compare(x, y) > 0
...
class Ops(lhs: T) {
def <(rhs: T) = lt(lhs, rhs)
def >(rhs: T) = gt(lhs, rhs)
...
}
implicit def mkOrderingOps(lhs: T): Ops = new Ops(lhs)
...
}
...
object Ordering extends LowPriorityOrderingImplicits {
def apply[T](implicit ord: Ordering[T]) = ord
...
trait IntOrdering extends Ordering[Int] {
def compare(x: Int, y: Int) =
if (x < y) -1
else if (x == y) 0
else 1
}
implicit object Int extends IntOrdering
...
trait StringOrdering extends Ordering[String] {
def compare(x: String, y: String) = x.compareTo(y)
}
implicit object String extends StringOrdering
...
}
Context Bound
Scala provides a typeclass pattern called Context Bound which represents such common pattern of passing in an implicit value:
// Implicit value passed in as implicit parameter
def someFunction[T](x: SomeClass[T])(implicit imp: AnotherClass[T]): WhateverClass[T] = {
...
}
With the context bound syntactic sugar, it becomes:
// Context Bound
def someFunction[T : AnotherClass](x: SomeClass[T]): WhateverClass[T] = {
...
}
The mergeSort function using context bound looks as follows:
// Generic Merge Sort using Context Bound
def mergeSort[T : Ordering](ls: List[T]): List[T] = {
val order = implicitly[Ordering[T]]
def merge(l: List[T], r: List[T]): List[T] = (l, r) match {
case (Nil, _) => r
case (_, Nil) => l
case (lHead :: lTail, rHead :: rTail) =>
if (order.lt(lHead, rHead))
lHead :: merge(lTail, r)
else
rHead :: merge(l, rTail)
}
val n = ls.length / 2
if (n == 0)
ls
else {
val (a, b) = ls splitAt n
merge(mergeSort(a), mergeSort(b))
}
}
Note that ‘implicitly[Ordering[T]]’ is there for access to methods in math.Ordering which is no longer passed in with a parameter name.
Scala’s math.Ordered versus math.Ordering
One noteworthy thing about math.Ordering is that it does not overload comparison operators ‘<‘, ‘>‘, etc, which is why method lt(x: T, y: T) is used instead in mergeSort for the ‘<‘ operator. To use comparison operators like ‘<‘, one would need to import order.mkOrderingOps (or order._) within the mergeSort function. That’s because in math.Ordering, comparison operators ‘<‘, ‘>‘, etc, are all defined in inner class Ops which can be instantiated by calling method mkOrderingOps.
Scala’s math.Ordered extends Java’s Comparable interface (instead of Comparator) and implements method compareTo(y: T), derived from math.Ordering’s compare(x: T, y: T) via implicit parameter. One nice thing about math.Ordered is that it consists of overloaded comparison operators.
The following highlights partial source code of math.Ordered:
// Scala's math.Ordered source code highlight
trait Ordered[A] extends Any with java.lang.Comparable[A] {
...
def compare(that: A): Int
def < (that: A): Boolean = (this compare that) < 0
def > (that: A): Boolean = (this compare that) > 0
def <= (that: A): Boolean = (this compare that) <= 0
def >= (that: A): Boolean = (this compare that) >= 0
def compareTo(that: A): Int = compare(that)
}
object Ordered {
implicit def orderingToOrdered[T](x: T)(implicit ord: Ordering[T]): Ordered[T] = new Ordered[T] {
def compare(that: T): Int = ord.compare(x, that)
}
}
Using math.Ordered, an implicit method, implicit orderer: T => Ordered[T], (as opposed to an implicit value when using math.Ordering) is passed to the mergeSort function as a curried parameter. As illustrated in a previous blog post, it’s an implicit conversion rule for the compiler to fall back to when encountering problem associated with type T.
Below is a version of generic Merge Sort using math.Ordered:
// Generic Merge Sort using implicit math.Ordered conversion
import math.Ordered
def mergeSort[T](ls: List[T])(implicit orderer: T => Ordered[T]): List[T] = {
def merge(l: List[T], r: List[T]): List[T] = (l, r) match {
case (Nil, _) => r
case (_, Nil) => l
case (lHead :: lTail, rHead :: rTail) =>
if (lHead < rHead)
lHead :: merge(lTail, r)
else
rHead :: merge(l, rTail)
}
val n = ls.length / 2
if (n == 0)
ls
else {
val (a, b) = ls splitAt n
merge(mergeSort(a), mergeSort(b))
}
}
scala> mergeSort(li) res5: List[Int] = List(3, 4, 5, 8, 9, 11, 12, 16) scala> mergeSort(ls) res6: List[String] = List(apple, banana, grape, guava, orange, peach, pear, strawberry)
View Bound
A couple of notes:
- The implicit method ‘implicit orderer: T => Ordered[T]’ is passed into the mergeSort function also as an implicit parameter.
- Function mergeSort has a signature of the following common form:
// Implicit method passed in as implicit parameter
def someFunction[T](x: SomeClass[T])(implicit imp: T => AnotherClass[T]): WhateverClass[T] = {
...
}
Such pattern of implicit method passed in as implicit paramter is so common that it’s given the term called View Bound and awarded a designated smiley ‘<%’. Using view bound, it can be expressed as:
// View Bound
def someFunction[T <% AnotherClass[T]](x: SomeClass[T]): WhateverClass[T] = {
...
}
Applying to the mergeSort function, it gives a slightly more lean and mean look:
// Generic Merge Sort using view bound
import math.Ordered
def mergeSort[T <% Ordered[T]](ls: List[T]): List[T] = {
def merge(l: List[T], r: List[T]): List[T] = (l, r) match {
case (Nil, _) => r
case (_, Nil) => l
case (lHead :: lTail, rHead :: rTail) =>
if (lHead < rHead)
lHead :: merge(lTail, r)
else
rHead :: merge(l, rTail)
}
val n = ls.length / 2
if (n == 0)
ls
else {
val (a, b) = ls splitAt n
merge(mergeSort(a), mergeSort(b))
}
}
As a side note, while the view bound looks like the other smiley ‘<:’ (Upper Bound), they represent very different things. An upper bound is commonly seen in the following form:
// Upper Bound
def someFunction[T <: S](x: T): R = {
...
}
This means someFunction takes only input parameter of type T that is a sub-type of (or the same as) type S. While at it, a Lower Bound represented by the ‘>:’ smiley in the form of [T >: S] means the input parameter can only be a super-type of (or the same as) type S.
