Question

Is there any reason why Scala's Ordering trait is not contravariant? A motivating example follows.

Suppose I want to perform an ordered insert. I may have a function with the signature

def insert[A, B >: A](list: List[A], item: A)(implicit ord: Ordering[B]): List[A]

Here, I have an Ordering which accepts super types of type A. I imagine this is useful when you're dealing with case classes. For example:

abstract class CodeTree
case class Fork(left: CodeTree, right: CodeTree, chars: List[Char], weight: Int) extends CodeTree
case class Leaf(char: Char, weight: Int) extends CodeTree

def weight(tree: CodeTree): Int 
def chars(tree: CodeTree): List[Char] 

implicit object CodeTreeOrdering extends Ordering[CodeTree] {
  def compare(a: CodeTree, b: CodeTree): Int = weight(a) compare weight(b)
}

I would want my insert function to work with types List[CodeTree], List[Leaf] or List[Fork]. However, as Ordering isn't contravariant, I need to define implicit Orderings for each case.

If I define

trait MyOrdering[-A] {
  def compare(a: A, b: A): Int
}

everything works as expected.

Is there any other way to achieve my goal?

EDIT:

My current solution is to define insert as

def insert[A](list: List[A], item: A)(implicit ord: Ordering[A]): List[A]

which works fine when dealing with List[CodeTree]. I also define (inspired by the scalaz library):

trait Contravariant[F[_]] {
  def contramap[A, B](r: F[A], f: B => A): F[B]
}

implicit object OrderingContravariant extends Contravariant[Ordering] {
  def contramap[A, B](r: Ordering[A], f: B => A) = r.on(f)
}

implicit def orderingCodeTree[A <: CodeTree]: Ordering[A] =
  implicitly[Contravariant[Ordering]].contramap(CodeTreeOrdering, identity)

I'm defining an implicit factory function for Ordering[A <: CodeTree] instances.

Was it helpful?

Solution

A bit more of a detailed answer pulled out of the thread on 'scala-language' linked in the comment above.

The reason that Ordering is not contravariant is that this doesn't accord sensibly with the notion of specificity used in implicit resolution. Implicit resolution will try to pick the most 'specific' type for a parameter, and considers one type to be more specific than another if it's a subtype of it. This makes sense in covariant cases: I'd rather have an implicit specific to my list of strings than one for any old list. In contravariant cases, however, it wants to pick the wrong thing:

trait Ord[-A]
A <: B
Ord[B] <: Ord[A]

And so it will pick the 'most specific' ordering as being, if available, Ordering[Any].

There looks to be a big discussion going on changing the way 'specificity' is defined with regard to contravariant parameters on the scala-language group.

OTHER TIPS

In the current API, these methods prevent it from being contravariant:

  /** Return `x` if `x` >= `y`, otherwise `y`. */
  def max(x: T, y: T): T = if (gteq(x, y)) x else y

  /** Return `x` if `x` <= `y`, otherwise `y`. */
  def min(x: T, y: T): T = if (lteq(x, y)) x else y
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top