There are two similar shapeless polymorphic functions in the following example. The only difference between them is that deserSucceeding
's implicit case definition has an additional subtyping evidence (implicit e: FS <: FromStr[T])
. Scala fails to derive an implicit Aux.Case
for deserFailing
, but succeeds for deserSucceeding
.
Why? Is this a limitation of the scala compiler or can deserSucceeding
lead to an ambiguity in the implicit derivation / type inference?
import shapeless._
type FromStr[T] = String => T
object FromDouble extends FromStr[Double] {
override def apply(v1: String): Double = v1.toDouble
}
object deserFailing extends Poly2 {
implicit def kase[T, FS <: FromStr[T]]: Case.Aux[String, FS, T] = at((s, fs) => fs(s))
}
// fails to derive a `Case.Aux`
deserFailing("1.0", FromDouble)
object deserSucceeding extends Poly2 {
implicit def kase[T, FS](implicit e: FS <:< FromStr[T]): Case.Aux[String, FS, T] = at((s, fs) => fs(s))
}
deserSucceeding("1.0", FromDouble)
The TL;DR; is how type inference works.
When you do [T, FS <: FromStr[T]]
the compiler tries to infer both at the same type and as such it probably end up inferring Any or Nothing from one of those to make the subtype constraint to type-check.
Whereas the second option doesn't force any subtype restriction during inference which makes the compiler to infer better types and then after that it will check the subtyping constraint.
It is similar to this example:
def tupleIfSubtype1[T <: U, U](t: T, u: U) = (t, u)
def tupleIfSubtype2[T, U](t: T, u: U)(implicit ev: T <:< U) = (t, u)
tupleIfSubtype1("Foo", 3) // compiles, it infers Any for the second element.
tupleIfSubtype2("Foo", 3) // doesn't compile.
More in-depth explanation, here.