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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With