I want to make the following trait contravariant in its type parameter.
trait Preferences[-A] {
def ordering: Ordering[A]
}
the issue with the above solution is that in Scala standard library Ordering
is invariant in its type parameter. This SO post has a discussion of why this is the case.
I have developed two solutions to my problem. The first solution is to make the parameter A
and upper bound.
trait Preferences[-A] {
def ordering[B <: A]: Ordering[B]
}
The second is to use implicit
.
trait Preferences[-A] {
def ordering(implicit ev: B <:< A): Ordering[B]
}
Both of these compile, but I don't understand the trade-offs. Is one of these approaches more general? Is there a third approach that I should be using instead?
Your first solution is considered best practice when trying to get around the problem that you have (which is having a contravariant parameter as return type of a method or a function).
Second solution is called a generalized type constraint and is mostly useful when you want to infer the correct types without having the compiler "fit them". For example, if you had
def foo[A, B <: A](a: A, b: B): Whatever = ???
and you tried to invoke it as
foo(42, "something")
the compiler would infer A
to be Any
, because that way the whole thing would fit; type B
, which is a String
, is indeed a subtype of Any
. With GTC:
def foo[A, B](a: A, b: B)(implicit ev: B <:< A)
compiler will infer them from your arguments as Int
and String
respectively. Only after that it checks the GTC, and here no expanding can take place because the parameter types have already been inferred. So the compilation will fail because String is not a subtype of Int.
As long as you explicitly define your A and B, and you always will (because there's nothing to infer them from), your first solution is fine. No need for implicits.