3
votes

I the following code, I have evidence of R[A] and B is a subtype of A, so I would expect foo to infer the type of A and use the RA evidence. However, scalac refuses to do so.

trait R[T]

case class A(i: Int)

object A {

  implicit object RA extends R[A]

}

class B(i: Int) extends A(i)

def foo[T](x : T)(implicit ev: R[T]) = 0

println(foo(new B(1))) // infers T as B and fails to find implicit R[B]

println(foo(new B(1) : A)) // Works, but undesirable

I tried this:

def foo[T, TT >: T](x : T)(implicit ev: R[TT]) = 0

But it still does not work.

Now, if I define:

def foo[T](x : T)(implicit ev: R[TT] forSome {type TT <: T}) = 0

inference works, but in my actual code I need to refer to TT.

Edit: now that I've moved A's evidence to the A companion object, this solution seems to not work anymore. In a realistic setting, the evidence will always be in the companion object and implicit search has to find it.

Another solution is to make my evidence contravariant but this causes a lot of trouble for me like inferring Nothing and other problems (My actual code is more complex than this simplified example).

How can I make this work correctly?

1
There is no nice solution AFAIK. Type inference isn't and can't be reliable in the presence of subtyping. Depending on your use case you might want to use a "smart constructor" (companion object apply method) for B that declares its return type as A, so that B(1) has type A.lmm
See the following conversation with Miles Sabin on the Shapeless Gitter channel: gitter.im/milessabin/shapeless?at=54df94f11443703854e6bc47Jonathan Chayat
This question came up while developing marblesJonathan Chayat

1 Answers

0
votes

You could use

def foo[T, TT](x : T)(implicit ev: R[_ >: T] with R[TT]) = 0

and refer to TT.

Edit: The following code uses contravariance to find the evidence in the companion object. It also encodes that TT is a supertype of T. It doesn't infer Nothing, but you've mentioned other problems with contravariance. Is it possible to work around those?

trait R[-T]

case class A(i: Int)

object A {

  implicit object RA extends R[A]

}

class B(i: Int) extends A(i)

def foo[T, TT](x : T)(implicit ev1: R[T] with R[TT], ev2: T <:< TT) = 0

println(foo(new B(1))) // infers TT as A