3

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?

  • 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 Feb 14 '15 at 19:27
  • See the following conversation with Miles Sabin on the Shapeless Gitter channel: https://gitter.im/milessabin/shapeless?at=54df94f11443703854e6bc47 – Jonathan Chayat Feb 14 '15 at 20:41
  • This question came up while developing [marbles](https://github.com/SupersonicAds/marbles/) – Jonathan Chayat Feb 21 '15 at 12:21

1 Answers1

0

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
  • Nice idea, but this does not encode the fact that `TT` is a supertype of `T`. Writing `def foo[T, TT >: T]` messes things up again. Also tried using `<:<` evidence without much success. Also, I've found out that using evidence of the form `ev: R[_ >: T]` messes up implicit search as it will not look in the companion object of `T`. – Jonathan Chayat Feb 21 '15 at 12:26
  • You could use `def foo[T, TT](x : T)(implicit ev1: R[_ >: T] with R[TT], ev2: T <:< TT) = 0` to encode that TT is a supertype of T. However, I don't know if there is a solution to find the implicit in the companion object that works for you. Would it be possible to do something like `trait R[T, TT]`, `object B { implicit object RA extends R[B, A] }` and `def foo[T, TT](x : T)(implicit ev1: R[T, TT], ev2: T <:< TT) = 0`? – Andreas Flueckiger Feb 21 '15 at 14:07
  • No, it is not possible for me to change `R`'s signature – Jonathan Chayat Feb 21 '15 at 16:04
  • Is it possible to make it contravariant as suggested in my edited answer? – Andreas Flueckiger Feb 21 '15 at 17:04
  • No. You are correct that in this question there is no problem with inferring `Nothing`. However, the code here is a simplified case of my real code (`HMap` in https://github.com/SupersonicAds/marbles), where making the equivalent of `R` contravariant causes `Nothing` to be inferred in `HMap.get` for example. – Jonathan Chayat Feb 24 '15 at 00:09
  • Does `def get[K, V, K1](key: K1)(implicit ev1: M[K1, V] with M[K, V], ev2: K1 <:< K): Option[V] = getMap[K, V].get(key)` with `trait M[-K, V]` work? – Andreas Flueckiger Feb 24 '15 at 13:54
  • You can actually make `V` contravariant too if you use `def get[K, V, K1, V1](key: K1)(implicit ev1: M[K1, V1] with M[K, V], ev2: K1 <:< K, ev3: V1 <:< V): Option[V] = getMap[K, V].get(key)`. – Andreas Flueckiger Feb 25 '15 at 08:44