Search code examples
scalagenericsimplicit

Why does the order of implicit parameters sometimes matter?


Regarding following piece of code (scala 2.12.10) my question is why order matter in that specific case for implicit arguments. I also doesn't understand why compiler tells me that there is ambiguous implicit values, i see any of hint about it.

import scala.reflect.ClassTag

trait Wrapper[T] {

  implicit def ct: ClassTag[T]

  // First try using scala syntaxic sugar for single type parameter type class
  def asPair1[K : ClassTag, V : ClassTag](implicit ev: T <:< (K, V)): Unit = Unit
  
  // Same as asPair1 with explicit implicits ClassTag, in same order than with syntaxic sugar version
  def asPair1Bis[K, V](implicit kt: ClassTag[K], vt: ClassTag[V], ev: T <:< (K, V)): Unit = Unit

  // Workaround
  def asPair2[K, V](implicit ev: T <:< (K, V), kt: ClassTag[K], vt: ClassTag[V]): Unit = Unit
  
}

trait Test[K, V] {
  
  implicit def kt: ClassTag[K]
  implicit def vt: ClassTag[V]
  
  val w: Wrapper[(K, V)]
  
  w.asPair1  // Fails
/**
error: ambiguous implicit values:
 both method kt in trait Test of type => reflect.this.ClassTag[K]
 and method vt in trait Test of type => reflect.this.ClassTag[V]
 match expected type reflect.this.ClassTag[K]
    w.asPair1  // Fails
      ^
error: No ClassTag available for K
    w.asPair1  // Fails
      ^
error: not enough arguments for method asPair1: (implicit   evidence$1: reflect.this.ClassTag[K], implicit   evidence$2: reflect.this.ClassTag[V], implicit  ev: $less$colon$less[scala.this.Tuple2[K,V],scala.this.Tuple2[K,V]])scala.this.Unit.
Unspecified value parameters evidence$1, evidence$2, ev.
    w.asPair1  // Fails
*/

  w.asPair1Bis // Fails
  w.asPair2  // Works


  val w2: Wrapper[(Int, Double)]
  w2.asPair1 // Fails with exact same logs than with `w`
  w2.asPair2


}

Solution

  • So first of all, do not mix context bounds and implicits, that is considered a bad practice; see this for more context.

    Second, the order matters because if the ev comes first then the type parameters K and V are solved by the compiler before it tries to search their ClassTags.
    In the other case, the compiler has no info about what K and V are so it will try to just assume anything it can find, thus finding two possible implicit ClassTags (both the one for the outer K and the outer V) creating an ambiguity.