从库函数调用特定于类型的代码,在编译时确定

时间:2016-03-23 15:43:44

标签: scala typeclass implicit

如何在Scala库中为调用者提供给该库的对象调用特定于代码类型的代码,其中决定调用哪个类型特定代码在编译时(静态),而不是运行时?

为了说明这个概念,假设我想创建一个库函数,如果为CanMakeDetailedString定义了.toString,则以一种方式打印对象,如果没有,则为nicePrint。请参阅此示例代码中的 import scala.language.implicitConversions trait CanMakeDetailedString[A] extends (A => String) def noDetailedString[A] = new CanMakeDetailedString[A] { def apply(a: A) = a.toString } object Util { def nicePrint[A](a: A) (implicit toDetail: CanMakeDetailedString[A] = noDetailedString[A]) : Unit = println(toDetail(a)) def doStuff[A](a: A) : Unit = { /* stuff goes here */ nicePrint(a) } }

  object Main {
    import Util._

    case class Rototiller(name: String)

    implicit val rototillerDetail = new CanMakeDetailedString[Rototiller] {
      def apply(r: Rototiller) = s"The rototiller named ${r.name}."
    }

    val r = Rototiller("R51")

    nicePrint(r)
    doStuff(r)
  }

现在这里有一些测试代码:

The rototiller named R51.
Rototiller(R51)

这是Scala 2.11.2中的输出:

nicePrint

当我从定义rototillerDetail的同一范围调用rototillerDetail时,Scala编译器会找到nicePrint并将其隐式传递给doStuff。但是,当我从相同的范围调用一个调用nicePrint的不同范围(rototillerDetail)的函数时,Scala编译器找不到implicit toDetail

毫无疑问,有充分的理由。不过,我想知道如何告诉Scala编译器"如果存在所需类型的对象,请使用它!"?

我可以想到两个解决方法,但这些解决方案都不令人满意:

  1. doStuff提供implicit toDetail参数。这是有效的,但是它要求我为每个函数添加一个CanMakeDetailedString参数,这些函数可能在调用堆栈中较低的位置用于Rototiller对象。这将大大混乱我的代码。

  2. 完全废弃隐式方法并以面向对象的方式执行此操作,通过覆盖CanMakeDetailedString这样的特殊新方法,使.toDetail继承implicit val

  3. 是否有一些技术,技巧或命令行开关可以使Scala编译器静态解析正确的隐式对象? (而不是在程序运行时动态地计算它,如在面向对象的方法中。)如果不是,这似乎严重限制了库代码可以使用多少"类别"或隐含的参数。换句话说,什么是良好的方式来做我上面做得很糟糕的事情?

    澄清:我不会问{1}如何做到这一点。我问你如何让Scala编译器在库代码中静态选择适合类型的函数,而不是在每个库函数中明确列出可能在栈中调用的每个函数的隐式参数。如果它是用implicits或其他任何东西完成的,对我来说并不重要。我只想知道如何编写在编译时适当选择类型特定函数的通用代码。

1 个答案:

答案 0 :(得分:0)

implicits在编译时被解析,因此在没有更多信息的情况下它无法知道doStuff中的A是什么。 该信息可以通过您建议的额外隐式参数或基本类型/接口提供。

您还可以在A类型上使用反射,使用返回子类型的getType,将对象强制转换为该类型,并调用具有为您写入字符串详细信息的类型名称的预定义函数。我不推荐它,因为任何OOP或FP解决方案都更好恕我直言。