Make Scala's implicitNotFound annotation more precise

时间:2015-09-30 23:15:31

标签: scala typeclass

I'm having some problems with typeclasses in Scala and more precisely with errors at compile time when a typeclass instance cannot be found. Let's say that 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 a TC[B[C]] for a C for which scalac cannot find an instance TC[C], scalac will give an error saying that it cannot find a TC[B[C]]. While this is true, the error message is missing the reason why scalac cannot find TC[B[C]], that is that it cannot find TC[C].

To illustrate the problem, I decided to make a small toy example 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 the error:

[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 is of course true: scalac can't find a pretty print instance for type Option[Unit]. The problem is that the error itself is not very helpful because the point is not that scalac can't find an instance of the type class but more why it cannot find it. In this case, the reason why Option[Unit] doesn't have a pretty print instance is because Unit doesn't 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 precise?

1 个答案:

答案 0 :(得分:0)

那会很好,但我认为你要解决的问题比初看起来时要复杂得多。在这个例子中,我为PrettyPrintable添加了一些更隐式的val和defs:

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
}

最后一行产生以下编译器错误,类似于示例中的错误:

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

在您的情况下,很容易因未找到隐式PrettyPrintable[Option[Unit]]而未找到隐式PrettyPrintable[Unit]。但是对于这一对,您是否想要责备没有找到隐含的PrettyPrintable[(Float, Long)]没有找到隐式PrettyPrintable[Float],没有找到隐式PrettyPrintable[Long],或者两者兼而有之?

请注意,隐式解决方案失败的根本原因甚至可能与原始版本的形状不同。例如,考虑添加以下两个隐式定义:

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"
}

现在,找不到隐式Int或找不到隐式String的根本原因是什么?实际答案非常复杂。像这样:

  • 没有隐式PrettyPrintable[(Float,Long)]因为:
    • 该精确类型没有隐含值,AND
    • PrettyPrintable[Float]PrettyPrintable[Long]都没有暗示
      • PrettyPrintable[Float]没有隐含,因为:
        • 该精确类型没有隐含值,AND
        • 没有隐式Int,AND
        • 没有隐含的String
      • PrettyPrintable[Long]没有隐含,因为:
        • 该精确类型没有隐含值

在回答你的问题时,"是否有可能使隐含的错误更加精确?",我认为这将涉及为您提供某种程序化的访问权限您可以在一般情况下更精确地构造错误消息。只为这项工作设计一个合适的API将是一项令人印象深刻的工作,更不用说实施该API了。

相关问题