I have a trait with a self-type annotation that has a type parameter. This trait is from a library and cannot be modified. I want to pass this trait to a function that will require an upper bound for the type parameter. For example, I have this code snippet:
sealed trait Job[K] { self =>
type T
}
case class Encoder[T <: Product]()
def encoder(job: Job[_])(implicit ev: job.T <:< Product): Encoder[job.T] =
new Encoder[job.T]()
This returns an error that Type argument job.T does not conform to upper bound Product and a warning that ev is never used. How should I design the encoder function?
Why it doesn't work?
Your issue has nothing to do with the generalized type constraint. You can remove it and still get the same error. A generalized type constraint is used to constrain the type of arguments the method can receive.
(implicit ev: job.T <:< Product)provides an evidence in scope that matches only ifjob.T <: Product, allowing only calls to the method withJobarguments wherejob.T <: Product. This is its purpose.Your issue is because the
Encoderclass has its type parameterT <: Product. The generalized type constraint does not treat the typejob.Titself as a subtype ofProduct, as you expected. The evidence only applies to value arguments, not to the type itself, because this is how implicit conversions work.For example, assuming a value
xof typejob.Tthat can be passed to the method as an argument:The first line compiles because
xis expanded toev.apply(x), but the second one cannot be expanded, regardless ifEncoderis covariant or not.First workaround
One workaround you can do is this:
The problem with this is that while both type parameters
UandTare subtypes ofProduct, this definition does not says much about the relation between them, and the compiler (and even Intellij) will not infer the correct resulting type, unless you specify it explicitly. For example:But why use
job.T <:< Productif we already haveU <: Product. We can instead use the=:=evidence to make sure their types are equal.Now the resulting type will be correctly inferred.
Second workaround
A shorter workaround is using a structural type instead:
Which is not only cleaner (doesn't require a generalized type constraint), but also avoids the earlier problem.
Both versions work on Scala 2.13.8.