Variance of type parameter in Ordered

I am wondering why the type parameter of Ordered trait is not contra-variant, although it is possible to use it in a contra-variant way. For Example:

class A(val x: Int) extends Ordered[A] {
override def compare(that: A): Int = this.x - that.x
}
case class B(y: Int) extends A(y) {
}
val b1 : B = new B(1)
val b2 : B = new B(2)
if (b1.compare(b2) < 0) {
println(“b1 < b2”)
} else {
println(“b1 >= b2”)
}

As can be seen, it is possible to compare Bs although the only implement Ordered[A]. This is a contra-variant relation, but trait Ordered is defined in a invariant way:

trait Ordered[A] extends Any with java.lang.Comparable[A]

I tried to find out in the documentation and the Programming in Scala book, but was not able to find an answer. Does somebody know?

Because Java? For whatever reasons, Ordered[A] extends Comparable[A], so it needs to be invariant.

trait VOrdered[-A] extends Any with Comparable[A] :
  def vcompare(that: A): Int
contravariant type A occurs in invariant position in type Any with Comparable[A] {...} of trait VOrdered

Without this legacy baggage it should be fine.

trait VOrdered[-A] extends Any :
  def vcompare(that: A): Int

trait Numbered extends Any with VOrdered[Numbered] :
  def number: Int
  def vcompare(that: Numbered): Int = number.compare(that.number)

case class PlainNumbered(number: Int) extends Numbered

def asContra(n: Int): VOrdered[PlainNumbered] = PlainNumbered(n)

println(asContra(42).vcompare(PlainNumbered(43))) // -1

Another reason is that variance and typeclasses don’t play well together.

So, it is usually better just to have a contraMap & narrow methods.

1 Like

Ordered[A] is plain OO inheritance. The type-classy counterpart (that I’d definitely prefer to use) would be Ordering[A], but this similarly ties in with java.lang.Comparator[A].

1 Like

Thanks for the explanations, that sounds reasonable. However, why does my example with the Bs still work? In Java we would have to use a contra-variant use-site variance annotation, which would be in Scala e.g. [T <: Ordered[_ >: T]]:

def max[T <: Ordered[_ >: T]](t1: T, t2: T) = if (t1 >= t2) then t1 else t2

But in Scala it also works without it, just with [T <: Ordered[T]]

def max[T <: Ordered[T]](t1: T, t2: T) = if (t1 >= t2) then t1 else t2

There must be some other ‘magic’.

Oh right, I mixed both (again), my bad.
I really never ever used Ordered, its usage is too cumbersome.

That is just plain-old subtyping, nothing fancy is happening there.

Actually, thinking about it, there isn’t much about contravariance in any of the examples and it would be weird in any case.

I tried to make an example to show how to use it in a contravariant way, but I couldn’t make it compile in any way; which makes sense because what I was trying to do is unsafe. Ordered is simply a badly designed abstraction, I wouldn’t bother about it.

Right. I just assumed that everything just works in terms of super type A.

However…

…now I’m somewhat confused. :thinking:

summon[B <:< Ordered[A]] // as expected: compiles
// summon[B <:< Ordered[B]] // as expected: Cannot prove that B <:< Ordered[B].

val b1: Ordered[B] = new B(1) // compiles O_o

Where am I going wrong - why does the Ordered[B] part work?

I would bet some form of implicit conversion back and forth.

There is something called reify or something like that, which you can use in the REPL to see what is happening.
Sorry for just telling what to do rather than doping it myself, I can’t at the moment :slight_smile:

Of course, thanks - should’ve thought of checking rogue implicits myself in the first place. :roll_eyes:

In its full, desugared beauty:

val b1: Ordered[B] = 
  Ordered.orderingToOrdered(new B(1))(Ordering.ordered($conforms))

:grinning:

1 Like

re: Ordering (as opposed to Ordered), some previous discussion is at Ordering[T] and friends should be contravariant · Issue #7179 · scala/bug · GitHub

1 Like

Thanks, this gives some insight.