scala implicit (5)

scala

Ordering[T]

Let’s learn the structure of a type class using Ordering[T] as an example.

Ordering is used to compare objects. All the methods are defined based on compare(x: T, y: T) method. I simplified the code and put it below.

trait Ordering[T] {
	def compare(x: T, y: T): Int
  /** other methods **/
	class OrderingOps(lhs: T) {
		def <(rhs: T) = lt(lhs, rhs)
		/** other operators **/
	}
  // implicit converter
	implicit def mkOrderingOps(t: T): OrderingOps = new OrderingOps(t)
}

object Ordering {
	trait ByteOrdering extends Ordering[Byte] {
		def compare(x: Byte, y: Byte) = x.toInt - y.toInt
	}
	// implicit instance
	implicit object Byte extends ByteOrdering
	/** other implicit objects **/

	// organize other implicit methods in inner scope
	trait ExtraImplicits {
		/** other methods **/
		implicit def infixOrderingOps[T](x: T)(implicit ord: Ordering[T]): Ordering[T]#Ops = new ord.Ops(x)
	}
	object Implicits extends ExtraImplicits{}
}

Key points:

  1. use inner class to add infix operators or add syntax to the wrapped type T.
  2. put all common implicit instances into companion objects. So they are in the implicit scope of type Ordering[T].
  3. organize implicit methods in trait and put it in the nested level of companion object. So those implicit methods need to be explicit imported when using.
case class Student(name: String, score: Double)
// implicit Ordering[Double] instance is in scope
// Ordering[Double] is actually Ordering.apply[Double](implicit Double instance)
val orderStudent: Ordering[Student] = Ordering.by[Student, Double](student => student.score)
import orderStudent.mkOrderingOps
//or 
// import Ordering.Implicits._
val s1 = Student("a", 90)
val s2 = Student("a", 80)
// implicit convert s1 to orderStudent.OrderingOps
println(s1 < s2)