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.