2

I have a question related to implicit priority system. I have following code:

object MyMain extends App {
  case class Plain(s: String)
  // cats version
  trait CatShow[T] extends ContravariantShow[T]

  object CatShow {
    def apply[A](implicit instance: CatShow[A]): CatShow[A] = instance

    trait ContravariantShow[-T] extends Serializable {
      def show(t: T): String
    }

    def show[A](f: A => String): CatShow[A] = new CatShow[A] {
      def show(a: A): String = f(a)
    }
  }
  // my simplified version
  trait MyShow[-T] extends Serializable {
    def show(t: T): String
  }

  object MyShow {
    def apply[A](implicit instance: MyShow[A]): MyShow[A] = instance

    def show[A](f: A => String): MyShow[A] = new MyShow[A] {
      def show(a: A): String = f(a)
    }
  }
  // implicits definition for both
  abstract class ImplicitInstances0 extends ImplicitInstances1 {
    implicit val catShowPlain: CatShow[Plain] = CatShow(_.toString + "[cat-plain]")
    implicit val myShowPlain: MyShow[Plain] = MyShow(_.toString + "[my-plain]")
  }

  abstract class ImplicitInstances1 {
    implicit val catShowAny: CatShow[Any] = CatShow(_.toString + "[cat-Any]")
    implicit val myShowAny: MyShow[Any] = MyShow(_.toString + "[my-Any]")
  }

  object ImplicitInstances extends ImplicitInstances0

  import ImplicitInstances._

  implicitly[CatShow[Plain]] // works magically
  implicitly[MyShow[Plain]] // [ERROR] compiler error for ambiguous implicit 

}

just wonder why the ContravariantShow will help the complier for prioritisation. Ideally, I would like to go through 2 cases step to step to show why one works and the other one fails.

Thanks

w_vv
  • 53
  • 3

1 Answers1

4

just wonder why the ContravariantShow will help the complier for prioritisation.

ContravariantShow doesn't help for prioritisation. If you remove it implicit will still resolve.

trait CatShow[T] /*extends ContravariantShow[T]*/ {
  def show(t: T): String // added
}

object CatShow {
  def apply[A](implicit instance: CatShow[A]): CatShow[A] = instance

//  trait ContravariantShow[-T] extends Serializable {
//    def show(t: T): String
//  }

  def show[A](f: A => String): CatShow[A] = new CatShow[A] {
    def show(a: A): String = f(a)
  }
}

What is important is variance of type class. CatShow is invariant and MyShow is contravariant. When you're looking for implicitly[CatShow[Plain]] only catShowPlain is a candidate. When you're looking for implicitly[MyShow[Plain]] both myShowAny (because implicitly[MyShow[Any] <:< MyShow[Plain]]) and myShowPlain are candidates. And they make ambiguity because of the reason Why is this implicit ambiguity behaviour happening?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Thanks for the answer especially the thread you posted which explains a lot to me. One last thing to understand is: the original `CatShow` can also handle `implicitly[CatShow[Random]]` if you have another random case class. So it should mean that the `CatShow[Any]` can somehow be eligible for `CatShow[Plain]`. This part confused me as well. – w_vv Mar 25 '20 at 15:52
  • 1
    @w_vv The original `CatShow` can't handle `implicitly[CatShow[Random]]` for `case class Random(i: Int)` if you don't add new implicits. `implicitly[CatShow[Random]]` doesn't compile. Write the whole code. – Dmytro Mitin Mar 25 '20 at 15:57
  • @w_vv `CatShow[Any]` and `CatShow[Plain]` are not connected types for invariant `CatShow`. – Dmytro Mitin Mar 25 '20 at 15:59
  • You are right...it seems like the other implicit make the show `StringContext` works in cats. Thanks a lot – w_vv Mar 25 '20 at 16:28