Make Scala implicitNotFound annotation more accurate

I am having problems with typeclasses in Scala and more precisely with errors during compilation when an instance of typeclass is not found. Say I have a typeclass TC and an object B[C] that has an instance of TC only if C has an instance of TC . In Scala, this can be written implicit def btc[C](implicit ctc: TC[C]) = new TC[B[C]] { ... } . When you need TC[B[C]] for C for which scalac cannot find an instance of TC[C] , scalac will give an error stating that it cannot find TC[B[C]] . Although this is true, there is no reason in the error message that scalac cannot find TC[B[C]] , that is, cannot find TC[C] .

To illustrate the problem, I decided to make a small example with a toy where TC is PrettyPrintable , C is Unit and B is Option :

 import scala.annotation.implicitNotFound @implicitNotFound("Cannot pretty print instances of the type ${T}") trait PrettyPrintable[T] { def prettyPrint(t: T): String } object PrettyPrintable { def apply[T](implicit pp: PrettyPrintable[T]): PrettyPrintable[T] = pp implicit def optPP[T](implicit opp: PrettyPrintable[T]) = new PrettyPrintable[Option[T]] { override def prettyPrint(ot: Option[T]): String = ot.fold("")(opp.prettyPrint) } } object Main extends App { println(PrettyPrintable[Option[Unit]].prettyPrint(None)) // error } 

If I run the application, I get an error message:

 [error] /home/rief/prog/scala/implicitNotFoundTest/Main.scala:24: Cannot pretty print instances of the type Option[Unit] [error] println(PrettyPrintable[Option[Unit]].prettyPrint(None)) 

This, of course, is true: scalac cannot find a pretty print instance for the type Option[Unit] . The problem is that the error itself is not very useful, because the point is not that scalac cannot find an instance of the type class, but even more, why it cannot find it. In this case, the reason Option[Unit] does not have a pretty print instance is because Unit does not have a pretty print instance, but for more complex cases this can be a nightmare.

My question is: is it possible to make errors on implicit, not found more accurate?

+6
source share
1 answer

That would be nice, but I think that the problem you want to solve is much more complicated than it seems at first glance. In this example, I added some more implicit vals and defs for PrettyPrintable :

 import scala.annotation.implicitNotFound @implicitNotFound("Cannot pretty print instances of the type ${T}") trait PrettyPrintable[T] { def prettyPrint(t: T): String } object PrettyPrintable { def apply[T](implicit pp: PrettyPrintable[T]): PrettyPrintable[T] = pp implicit val intPP = new PrettyPrintable[Int] { override def prettyPrint(i: Int): String = s"== $i ==" } implicit def optPP[T](implicit opp: PrettyPrintable[T]) = new PrettyPrintable[Option[T]] { override def prettyPrint(ot: Option[T]): String = s"-- ${ot.map(opp.prettyPrint)} --" } implicit def pairPP[T, U](implicit tpp: PrettyPrintable[T], upp: PrettyPrintable[U]) = new PrettyPrintable[(T, U)] { override def prettyPrint(pair: (T, U)): String = s"[[[ ${tpp.prettyPrint(pair._1)} >>> ${upp.prettyPrint(pair._2)} ]]]" } } object Main extends App { println(PrettyPrintable[Int].prettyPrint(6)) // prints == 6 == println(PrettyPrintable[Option[Int]].prettyPrint(None)) // prints -- None -- println(PrettyPrintable[Option[Int]].prettyPrint(Some(6))) // prints -- Some(== 6 ==) -- println(PrettyPrintable[(Int,Int)].prettyPrint((6 -> 7))) // prints [[[ == 6 == >>> == 7 == ]]] println(PrettyPrintable[(Float,Long)].prettyPrint((6F -> 7L))) // error } 

The last line produces the following compiler error, similar to the error from your example:

 Cannot pretty print instances of the type (Float, Long) 

In your case, it's easy to blame for not finding the implicit PrettyPrintable[Option[Unit]] without finding the implicit PrettyPrintable[Unit] . But in the case of a couple, would you like to blame that you did not find the implicit PrettyPrintable[(Float, Long)] without detecting the implicit PrettyPrintable[Float] , without finding the implicit PrettyPrintable[Long] or both?

Please note that the main reason for implicit permission cannot even have the same form as the original. For example, consider adding the following two implicit defs:

 implicit def i2ppf(implicit i: Int) = new PrettyPrintable[Float] { override def prettyPrint(f: Float): String = s"xx $f xx" } implicit def s2ppf(implicit s: String) = new PrettyPrintable[Float] { override def prettyPrint(f: Float): String = s"xx $f xx" } 

Now, is there a reason not to find the implicit Int , or not to find the implicit String ? The actual answer is very complex. Something like that:

  • There was no implicit PrettyPrintable[(Float,Long)] , because:
    • there was no implicit value with this exact type, AND
    • there were no signs for PrettyPrintable[Float] and PrettyPrintable[Long]
      • PrettyPrintable[Float] not implied, because:
        • there was no implicit value with this exact type, AND
        • there was no implicit Int , and
        • there was no implicit String
      • PrettyPrintable[Long] not implied, because:
        • there was no implicit value with this exact type

In response to your question: β€œIs it possible to make errors implicitly, no more accurate?”, I think this is due to giving you a certain programmatic access to a wide tree of possibilities for creating an error message more precisely in the general case. Just developing a decent API to work with would be a pretty impressive start, not to mention implementing that API.

0
source

All Articles