Scalaz Order

In this post we’ll look at how to implement ordering using Scalaz Order trait. However, before we do that, let’s step back a little and look at how we implement ordering using scala.math.Ordering

Sorting a Collection - Scala Way

Say we have a List and we’d like to sort it.[1] We can do so by calling the sorted method on it and it’ll work out-of-the-box for lists of Int, Float, etc. because there is an implicit in scala.math.Ordering.

1
2
@ List(1, 2, 3, 5, 4, 10, -1, 0).sorted
res0: List[Int] = List(-1, 0, 1, 2, 3, 4, 5, 10)

Going over the documentation for scala.math.Ordering[2]:

Ordering is a trait whose instances each represent a strategy for sorting instances of a type.
Ordering’s companion object defines many implicit objects to deal with subtypes of AnyVal (e.g. Int, Double), String, and others.

All the implicit objects in the companion object implement the scala.math.Ordering trait which extends the java.util.Comparator interface. Thus, they all have a compare method. What if we have types for which there is no sorting strategy in the companion object of scala.math.Ordering? We define our own like so:

1
2
3
4
5
6
7
8
9
@ case class Salary(amt: Float)
defined class Salary
@ implicit object SalaryOrdered extends Ordering[Salary] {
// we are using compare on type Float
def compare(a: Salary, b: Salary): Int = a.amt compare b.amt
}
defined object SalaryOrdered
@ List(Salary(999.0f), Salary(555.0f)).sorted
res4: List[Salary] = List(Salary(555.0F), Salary(999.0F))

But we still don’t have operators like <, <=, etc. on type Salary.

1
2
3
4
5
@ Salary(999.0f) > Salary(555.0f)
cmd3.sc:1: value > is not a member of ammonite.$sess.cmd0.Salary
val res3 = Salary(999.0f) > Salary(555.0f)
^
Compilation Failed

To do that, the trait scala.math.Ordered would have to be mixed in.

1
2
3
4
5
6
7
8
@ case class Salary(amt: Float) extends Ordered[Salary] {
override def compare(that: Salary): Int = this.amt compare that.amt
}
defined class Salary
@ Salary(1.0f) < Salary(2.0f)
res1: Boolean = true
@ List(Salary(2.0f), Salary(2.0f), Salary(1.0f)).sorted
res2: List[Salary] = List(Salary(1.0F), Salary(2.0F), Salary(2.0F))

And we know that if we’re working with a library, we do not have the liberty to mix a trait for our convenience. Also, Scala’s comparison operators are not type safe.

1
2
@ 1 > 2.0
res4: Boolean = false

Sorting a Collection - Scalaz Way

As stated before, Scalaz provides Order trait. Staying with our Salary example, let’s put Scalaz Order to use.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ import scalaz._
import scalaz._
@ import Scalaz._
import Scalaz._
@ case class Salary(amt: Float)
defined class Salary
@ implicit object SalaryOrder extends Order[Salary] {
override def order(a: Salary, b: Salary): Ordering = {
a.amt compare b.amt match {
case -1 => Ordering.LT
case 0 => Ordering.EQ
case 1 => Ordering.GT
}
}
}
defined object SalaryOrder

We’ve defined a way to order Salary objects using Scalaz Order trait via a typeclass. The implicit object needs to provide an implementation for order method which returns an Ordering value. Going over the code for Scalaz Ordering:

This Ordering is analogous to the Ints returned by scala.math.Ordering.

Now, we have comparison operators at our disposal. Here’s how we can compare Salary objects using the ?|? operator:

1
2
@ Salary(1.0f) ?|? Salary(2.0f)
res4: Ordering = LT

?|? can also be used with Int, Float, etc. and is type-safe.

1
2
3
4
5
6
7
@ 1.0 ?|? 2.0
res5: Ordering = LT
@ 1.0 ?!? 2
cmd6.sc:1: value ?!? is not a member of Double
val res6 = 1.0 ?!? 2
^
Compilation Failed

Also, analogous to <, <=, we now have type-safe lt, lte, etc.

1
2
3
4
5
6
7
8
9
@ 1.0 gte 2.0
res6: Boolean = false
@ 1 gt 2.0
cmd8.sc:1: type mismatch;
found : Double(2.0)
required: Int
val res8 = 1 gt 2.0
^
Compilation Failed

That’s not where the power ends. You can also compare Options seamlessly. Do note that it is some with a lowercase “s”.

1
2
@ some(Salary(1.0f)) ?|? some(Salary(2.0f))
res8: Ordering = LT

You can now also use sorted on a collection equally easily. Using Order[A].toScalaOrdering we can get back an object of type scala.math.Ordering which we can use to sort collections.

1
2
3
4
@ implicit val salaryOrdering = Order[Salary].toScalaOrdering
salaryOrdering: math.Ordering[Salary] = scalaz.Order$$anon$1@5b8a5d7c
@ List(Salary(2.0f), Salary(1.0f), Salary(0.0f)).sorted
res10: List[Salary] = List(Salary(0.0F), Salary(1.0F), Salary(2.0F))

Also, since Order trait extends the Equal trait, you get the === operator, too.

1
2
@ Salary(0.0F) === Salary(1.0F)
res11: Boolean = false

Conclusion

By using Scalaz Order trait, we can implement comparison between values in a type-safe and extensible way. There’s seamless transformation from Scalaz Order to scala.math.Ordering which lets us use the sorted method on collections.