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

Scala 从库函数调用特定于类型的代码,在编译时确定,scala,typeclass,implicit,Scala,Typeclass,Implicit,如何在Scala库中为调用者提供给该库的对象生成特定于类型的代码,其中关于调用哪种特定于类型的代码的决定是在编译时(静态)而不是在运行时做出的 为了说明这一概念,假设我想创建一个库函数,如果为对象定义了CanMakeDetailedString,则以一种方式打印对象,如果没有,则以.toString的方式打印对象。请参见本示例代码中的nicePrint: import scala.language.implicitConversions trait CanMakeDetailedStr

如何在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)
  }
以下是Scala 2.11.2中的输出:

The rototiller named R51.
Rototiller(R51)
当我从定义
rototillerDetail
的同一范围调用
nicePrint
时,Scala编译器会找到
rototillerDetail
,并将其隐式传递给
nicePrint
。但是,当我从同一个作用域调用另一个作用域(
doStuff
)中调用
nicePrint
)的函数时,Scala编译器找不到
rototillerdeail

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

我可以想出两种变通办法,但都不令人满意:

  • doStuff
    提供一个
    隐式toDetail
    参数。这是可行的,但它需要我为每个函数添加一个
    隐式toDetail
    参数,这些函数可能在调用堆栈的较低位置使用
    CanMakeDetailedString
    对象。这将极大地扰乱我的代码

  • 完全放弃隐式方法,并以面向对象的方式执行此操作,通过重写一个特殊的新方法,如
    .toDetail
    ,使
    Rototiller
    继承自
    CanMakeDetailedString

  • 是否有一些技术、技巧或命令行开关可以使Scala编译器静态解析正确的隐式对象?(而不是像面向对象方法那样,在程序运行时动态地计算出来。)如果不是,这似乎严重限制了库代码对“类型类”或隐式参数的使用。换句话说,做我在上面做得不好的事情的好方法是什么



    澄清:我不是问如何使用
    隐式val
    实现这一点。我想问的是,如何让Scala编译器在库代码中静态地选择适合类型的函数,而不在每个库函数中明确列出可能在堆栈较低位置调用的每个函数的隐式参数。这对我来说并不重要,不管是用隐式还是其他什么。我只想知道如何编写泛型代码,以便在编译时适当地选择特定于类型的函数。

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


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

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


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

    这是否意味着我所要求的无法完成(静态选择正确的函数等)?如果我理解正确(希望我是错的),那么类型类在Scala中基本上是残废的,除非你“显式地”让每个函数为堆栈底层可能需要的所有类型类取隐式参数。这就是我的理解。另一个解决方案是在nicePrint中打开a的类型开关。似乎Haskell在这方面做得更好。这是否意味着我所要求的不能完成(静态地选择正确的函数,等等)?如果我理解正确(希望我是错的),那么类型类在Scala中基本上是残废的,除非你“显式地”让每个函数为堆栈底层可能需要的所有类型类取隐式参数。这就是我的理解。另一个解决方案是在nicePrint中打开a的类型开关。哈斯凯尔似乎在这方面做得更好。这里有一个相关的讨论:那么这个问题得到了回答吗?@slouc我不知道。也许问题已经解决了。这里有一个相关的讨论:那么这个问题得到了回答吗?@slouc,我不知道。也许问题已经解决了。