Scala:为反射/运行时实例使用正确的类型类实例 [英] Scala: Using right type class instance for reflected/runtime instance

查看:39
本文介绍了Scala:为反射/运行时实例使用正确的类型类实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的特质

trait BusinessObject {}

和一个简单的类型类

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

在我的代码库中,我有几百个 BusinessObject 的实现.有些是直接实现者,有些实现了 BusinessObject 的子特征,有些使用 with 添加了各种混合特征.我有大约 10 种不同的 Printer 特殊实现(在各种子特征和 mixin 上定义),以及任何其他 BusinessObject 的低优先级通用回退实例,它可以工作一种魅力.

In my code base I have a few hundred implementations of BusinessObject. Some are direct implementers, some implement sub-traits of BusinessObject, and some add various mixin traits using with. I have around 10 different special implementations of Printer (defined on the various sub-traits and mixins), and a low-priority generic fallback instance for any other BusinessObject and it works a charm.

我需要在代码库中记录 BusinessObject 的所有实现,所以我使用了 Scala 的反射机制来枚举这些,现在想在每个实现上应用一个打印机.反射机制的方法签名是

I need to document all the implementations of BusinessObject in the code base, so I have used Scala's reflection mechanism to enumerate these, and now want to apply a Printer on each. The method signature of the reflection mechanism is

def enumerateBOs: Traversable[BusinessObject]

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

It returns one instance of each BusinessObject implementation. My problem is that at runtime there seems to be no way to get the right (specific) Printer for each object in this traversable.

我尝试过使用 .type 进行召唤,如下所示:

I have tried summoning using .type like this:

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

但我得到了每个元素的通用后备Printer.

but I get the generic fallback Printer for every element.

有什么方法可以做我想做的事吗?或者,如果隐式真的只在编译时可用,有什么方法可以在编译时列出 BusinessObject 的所有实现者?

Is there some way to do what I want to do? Or, if implicits really only are available at compile time, some way to list all implementers of BusinessObject at compile time?

推荐答案

隐式(如同 Scala 中的所有泛型)实际上是一种编译时机制,不可能在编译时定位非密封特征的所有实现.

Implicits (as all generics in Scala) are really a compile-time mechanism and it's not possible to locate all implementations of a non-sealed trait at compile time.

话虽如此,在运行时运行 Scala 编译器并不难.

That being said, it's not hard to run Scala compiler at runtime.

获取您的依赖项:

libraryDependencies ++= Seq(
  "org.scala-lang" % "scala-reflect" % "2.12.3",
  "org.scala-lang" % "scala-compiler" % "2.12.3"
)

您只需要一个 ToolBox 对象即可完成所有操作 - 它解析一个字符串,然后将解析后的树编译为一个函数 () =>Any,在调用时给出表达式的结果.代码也无法访问周围的上下文,因此所有类型都必须完全限定或导入.

You only need a ToolBox object to get everything done - it parses a string, then compiles parsed tree to a function () => Any, which, when called, gives the result of an expression. The code does not have an access to surrounding context too, so all types have to be fully qualified or imported.

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的强制转换是否有效,因此如果使用不当,您可能会在未知的地方得到ClassCastExceptions.

The above function throws exceptions and does not really check if the cast to A is a valid one, so you can get ClassCastExceptions in unknown places if not used correctly.

但现在,在运行时获取实例只是几个 LOC 的问题:

But now, getting instances at runtime is just a matter of a few LOCs:

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 并且在这种情况下您需要一些后备.速度也很慢.

I'm assuming you're not using anonymous classes - their getCanonicalName returns null and you'll need some fallback in that case. It's rather slow too.

这篇关于Scala:为反射/运行时实例使用正确的类型类实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆