scalatype-inferencetypeclassscala-implicits

Scala type parameter inference based on return type (function vs method)


I try to create a small matching library in Scala. I have the following type representing a matcher that reprsents a constraint on a type T:

trait Matcher[-T] extends (T => Boolean)

and a matches function that checks whether that constraint holds on a given instance:

def matches[A](x: A, m: Matcher[A]) = m(x)

With this I would like to be able to write checks like:

matches(Option(1), contains(1))
matches(Seq(1,2), contains(1))

where the contains can abstract over any container. I tried the following abstraction using type classes:

trait F[-C[_]] {
  def contains[A >: B, B](c: C[A], x: B): Boolean 
}

which I can then use to define contains function:

def contains[A, B[A]](y: A)(implicit f: F[B]): Matcher[B[A]] = new Matcher[B[A]] {
  override def apply(v1: B[A]): Boolean = f.contains(v1, y)
}

with two implicit definitions, one for Option:

implicit object OptionF extends F[Option] {
  override def contains[A >: B, B](c: Option[A], x: B): Boolean = c.contains(x)
}

and for Iterable:

implicit object IterableF extends F[Iterable] {
  override def contains[A >: B, B](c: Iterable[A], x: B): Boolean = c.exists(_ == x)
}

However, when I get the errors on each call to matches. They are all the same:

Error:(93, 39) ambiguous implicit values:
both object OptionF in object MatchExample of type MatchExample.OptionF.type
and object IterableF in object MatchExample of type MatchExample.IterableF.type
match expected type MatchExample.F[B]
matches(Option(1), contains(1))

It seems that the type inference could not deduce the type properly and that is why both implicit matches.

How can the matches function be defined without ambiguity?

I also tried to use implicit conversion to add the matches function directly to any type:

implicit class Mather2Any[A](that:A) {
  def matches(m: Matcher[A]): Boolean = m(that)
}

and that is working just fine:

Option(x1) matches contains(x1)
lx matches contains(x1)
lx matches contains(y1)

What I don't understand why the matches functions does not work while the method does? It looks like the problem is that the inference is only based on the return type. For example instead of contains I could isEmpty with not arguments which again works with the matches method but not function.

The full code listing is in this gist.


Solution

  • What you need is to split the parameters into two lists:

    def matches[A](x: A)(m: Matcher[A]) = m(x)
    
    matches(Option(1))(contains(1))
    matches(Seq(1,2))(contains(1))
    

    A gets inferred from x and is then available while type-checking m. With Mather2Any you have the same situation.

    Side note: variations on this get asked often, I just find it faster to reply than to find a duplicate.