Scala:为反射/运行时实例使用正确的类型类实例 [英] Scala: Using right type class instance for reflected/runtime instance
问题描述
我有一个简单的特质
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
的强制转换是否有效,因此如果使用不当,您可能会在未知的地方得到ClassCastException
s.
The above function throws exceptions and does not really check if the cast to A
is a valid one, so you can get ClassCastException
s 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屋!