Unexpected implicit resolution based on inference from return type
Given a typeclass where instance selection should be performed based on the return type:
case class Monoid[A](m0: A) // We only care about the zero here
implicit def s[T] : Monoid[Set[T]] = Monoid(Set.empty[T])
implicit def l[T] : Monoid[List[T]] = Monoid(List.empty[T])
def mzero[A](implicit m: Monoid[A]) : A = m.m0
why does Scala (2.11.6) fail to resolve the proper instance:
scala> mzero : List[Int]
<console>:24: error: ambiguous implicit values:
both method s of type [T]=> Monoid[Set[T]]
and method l of type [T]=> Monoid[List[T]]
match expected type Monoid[A]
mzero : List[Int]
^
when it has no problems finding an implicit based on the return type when using the implicitly function (we redefine it here as i to illustrate how similar it is to mzero)
def i[A](implicit a : A) : A = a
scala> i : Monoid[List[Int]]
res18: Monoid[List[Int]] = Monoid(List())
The Monoid[A]
, instead of Monoid[List[Int]]
in the error message is puzzling.
I would assume many scalaz contributors to be familiar with this problem as it seems to limit the convenience of typeclasses in scala.
EDIT: I'm looking into getting this working without forgoing type inference. Otherwise I'd like understand why that's not possible. If this limitation is documented as a Scala issue, I could not find it.
Solution 1:
1) After rewriting your code as follows:
case class Monoid[A](m0: A) // We only care about the zero here
implicit def s[T] : Monoid[Set[T]] = Monoid(Set.empty[T])
implicit def l[T] : Monoid[List[T]] = Monoid(List.empty[T])
def mzero[A]()(implicit m: Monoid[A]) : A = m.m0
val zero = mzero[List[Int]]()
val zero2: List[Int] = mzero()
then becomes clearly why that works so.
2) After you had to set mzero as def mzero[A]()(implicit m: Monoid[_ <: A]) : A = m.m0
then you enabled additional type inference to resolve existencial type. Compiler got actual type from required return type.
You could check it with def mzero[A <: B, B]()(implicit m: Monoid[A]) : A = m.m0
if you want.
3) Of course all that behaviour is just subtleties of compiler and I not think that such partial cases really required deep understanding.