Scala:使用正确的类型类实例进行反射/运行时实例

4

我有一个简单的特点


trait BusinessObject {}

以及一个简单的类型类

trait Printer[T<:BusinessObject] { def print(instance:T): Unit }

在我的代码库中,我有几百个BusinessObject的实现。有些是直接实现者,有些实现子trait BusinessObject,还有一些使用with添加各种mixin traits。我有约10个不同的Printer特殊实现(在各种子trait和mixin上定义),以及一个低优先级的通用fallback实例,适用于任何其他BusinessObject,效果很好。
我需要记录代码库中所有BusinessObject的实现,因此我使用Scala的反射机制枚举这些实现,并想要对每个实现应用一个Printer。反射机制的方法签名为:
def enumerateBOs: Traversable[BusinessObject]

它返回每个BusinessObject实现的一个实例。我的问题是,在运行时似乎没有办法为可遍历对象中的每个对象获取正确(特定)的打印机。

我已尝试使用.type发出召唤,像这样:

enumerateBOs.head match { case bo => Printer[bo.type].print(bo) }

但是我得到了每个元素的通用回退 Printer

有没有办法做我想做的事情?或者,如果隐式只在编译时可用,有没有办法在编译时列出所有实现者 BusinessObject 的方式?

1个回答

1
隐式参数(和Scala中的所有泛型一样)实际上是一种编译时机制,无法在编译时定位非密封特质的所有实现。
说到这里,运行Scala编译器并不难。获取您的依赖项:
libraryDependencies ++= Seq(
  "org.scala-lang" % "scala-reflect" % "2.12.3",
  "org.scala-lang" % "scala-compiler" % "2.12.3"
)

您只需要一个 ToolBox 对象就可以完成所有操作 - 它解析字符串,然后将解析树编译为函数 () => Any,调用该函数会给出表达式的结果。代码也没有访问周围上下文的权限,因此所有类型都必须被完全限定或导入。
import scala.reflect.runtime._
import scala.tools.reflect.ToolBox
import scala.util.Try

def unsafeCompile[A](code: String): A = {
  val tb = currentMirror.mkToolBox()
  tb.compile(tb.parse(code))().asInstanceOf[A]
}

上述函数会抛出异常,实际上并未检查向A的转换是否有效,因此,如果使用不正确,则可能在未知位置引发ClassCastException异常。
但现在,在运行时获取实例只需要几行代码。
enumerateBOs.map { obj =>
  Try {
    val f = unsafeCompile[Any => Unit](s"""
      import your.package_.with_.Printer
      // any additional imports for instances go there too

      implicitly[Printer[_root_.${obj.getClass.getCanonicalName}]].print _
    """)
    f(obj)
  }
}

我假设你没有使用匿名类 - 它们的getCanonicalName返回null,在这种情况下需要一些备选方案。它也相当慢。

那是一个非常棒的答案。既回答了实际问题,又提供了不同的解决方案。谢谢。 - holbech
对于以后查看此内容的任何人:为了使我的真实示例正常工作,我不得不从.compile切换到使用.eval,并将implicitly[Printer[_root_.${obj.getClass.getCanonicalName}]].print _这一行放入花括号中。 - holbech

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接