2

I am writing the following code to illustrate the problem:

def max[T <% Ordered[T]](a: T, b: T) = {
  val c = a.compare(b)
  if (c > 0) a else b
}

def min[T <% Ordering[T]](a: T, b: T) = {
    val ord = implicitly[Ordering[T]]
    val c = ord.compare(a, b)
    if (c > 0) b else a
}

println(s"max is ${max(10, 20)}")
implicit val intOrdering = new Ordering[Int] {
  override def compare(x: Int, y: Int): Int = x - y
}
println(s"min is ${min(10, 20)}")

The max method works well, while the min method doesn't, complaining No implicit Ordering defined for T even though I defined the intOrdering, it still complains.

I would ask why Ordered works, but Ordering doesn't here, even if I have provided the implicit definition for the Ordering[Int]

HTNW
  • 27,182
  • 1
  • 32
  • 60
Tom
  • 5,848
  • 12
  • 44
  • 104

2 Answers2

4

Ordered and Ordering are semantically different. An Ordered[T] is an object that supports comparisons against other Ordered[T]s, while an Ordering[T] is a single object that is a collection of functions able to compare ordinary Ts.

This difference is reflected in the two types of bounds you need to use here. The view bound [T <% Ordered[T]] means that there needs to be an implicit function T => Ordered[T] somewhere in scope. This function is the implicit conversion intWrapper(Int): RichInt. These RichInts directly support comparisons between themselves via the comparison methods.

However, def min[T <% Ordering[T]]... can't work. This requires a T => Ordering[T], or a function from values of a type to orderings on that type. For some types this might exist, but for most, like Int, it won't.

The other kind of bound is the context bound, T: Ordering, which is what you must use in min. A context bound of this form requests an implicit Ordering[T], that is, def min[T: Ordering](a: T, b: T) takes two Ts as arguments, and also one implicit Ordering[T] that defines how they are ordered.

In mathier language: A view bound is of the form Left <% Right for some types Left and Right of kind *, leading to the creation of an implicit parameter of type Left => Right. A context bound is of the form Left: Right for some type Left of some kind k and some type Right of kind k -> *, leading to the creation of an implicit parameter of type Right[Left].

HTNW
  • 27,182
  • 1
  • 32
  • 60
2

The view bound used in your max method is really just a short-cut for:

def max[T](a: T, b: T)(implicit ord: T => Ordered[T]) = {
  val c = a.compare(b)
  if (c > 0) a else b
}

Ordering requires an implicit value (as opposed to an implict function for Ordered) for your min method as follows, thus view bound isn't applicable:

def min[T](a: T, b: T)(implicit ord: Ordering[T]) = {
  val c = ord.compare(a, b)
  if (c > 0) b else a
}

Expressing the above in the form of context bound yields the following:

def min[T: Ordering](a: T, b: T) = {
  val ord = implicitly[Ordering[T]]
  val c = ord.compare(a, b)
  if (c > 0) b else a
}
Leo C
  • 22,006
  • 3
  • 26
  • 39