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
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].
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
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.
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