【问题标题】:Scala reflection to resolve generic trait from name at runtimeScala反射在运行时从名称解析通用特征
【发布时间】:2016-08-05 08:12:24
【问题描述】:

我有两个特征及其实例对象定义如下:

package reflection

trait Monoid[T] {
  def id: T
  def op(lhs: T, rhs: T): T
}

trait ADTHelper[T]{
  type V
  def create(value: V): T
  def get(adt: T): Any
}

case class Avg(avg: Double, n: Int)

object AvgMonoid extends Monoid[Avg] with ADTHelper[Avg]{
  override def id: Avg = Avg(0, 0)

  override def op(lhs: Avg, rhs: Avg): Avg =
    Avg( (lhs.avg * lhs.n + rhs.avg * rhs.n)/(lhs.n + rhs.n), lhs.n + rhs.n)

  override type V = Double
  override def create(value: Double): Avg = Avg(value, 1)
  override def get(adt: Avg): Any = adt.avg
}

object MinMonoid extends Monoid[Double] with ADTHelper[Double]{
  override def id: Double = Double.MaxValue

  override def op(lhs: Double, rhs: Double): Double = if(lhs < rhs) lhs else rhs

  override type V = Double

  override def create(value: Double): Double = value

  override def get(adt: Double): Any = adt
}

我想在运行时从名称中获取 monoid 实例。例如,如果我说"min",我想要MinMonoid 对象,"avg" 应该给AvgMonoid 对象等。所以我有以下设置:

object Test extends App {

  val AGGREGATORS_NAME_DICT = Map(
    "avg" -> "reflection.AvgMonoid",
    "min" -> "reflection.MinMonoid",
    "max" -> "reflection.MaxMonoid"
  )
  val AGGREGATORS_ADT_DICT = Map(
    "avg" -> "reflection.Avg",
    "min" -> "scala.Double",
    "max" -> "scala.Double"
  )

  val mirror = runtimeMirror(getClass.getClassLoader)

  def stringToTypeTag[A](name: String): TypeTag[A] = {
    val tpe = mirror.staticClass(name).selfType
    TypeTag(mirror, new api.TypeCreator {
      def apply[U <: api.Universe with Singleton](m: api.Mirror[U]) =
        if (m eq mirror) tpe.asInstanceOf[U#Type]
        else throw new IllegalArgumentException(s"Type tag defined in $mirror cannot be migrated to other mirrors.")
    })
  }

  def resolve[T](fname: String): Option[Monoid[T] with ADTHelper[T]] = for {
    monoidName <- AGGREGATORS_NAME_DICT.get(fname)
    adtName <- AGGREGATORS_ADT_DICT.get(fname)
    tag <- Option{stringToTypeTag[T](adtName)}
    instance <- Option {
      mirror
        .reflectModule(mirror.staticModule(monoidName))
        .instance
        .asInstanceOf[Monoid[T] with ADTHelper[T]]
    }
  } yield instance
}

现在的问题是: 我能做到:

println(resolve("min").get.op(1.0, 2.0))

但我做不到:

val monoid = resolve("min").get
println(monoid.op(1.0, 2.0))

因为后面monoid的类型是Monoid[Nothing] with ADTHelper[Nothing]。我无法使用我想出的resolve 方法解析trait Monoid[T]trait ADTHelper[T] 的基础类型T。如何修改 resolve 函数,以便它解析具有基础类型 T 的特征?

我知道如果我使用resolve[Double](...) 调用,它会起作用,但我希望它在运行时从AGGREGATORS_ADT_DICT 解决。

【问题讨论】:

    标签: scala generics reflection


    【解决方案1】:

    在运行时解析T 是不可能的,因为没有T 可以解析!当您编写resolve("min") 时,编译器 在编译期间确定其类型参数。并且因为参数没有告诉它关于T 的任何信息,并且上下文没有提供预期的类型(例如val monoid: Monoid[Double] = resolve("min"),它将推断Nothing。注意resolve 的代码没有'在这里根本不重要,只有它的类型签名。

    您可以将resolve 设为宏,从而在宏运行时确定T,即使用此宏的程序的编译时间。但这仅适用于您在编译时知道名称的情况(例如,它是一个文字字符串)。

    【讨论】:

    • 感谢您的评论。所以,这是由于 JVM 中的擦除,对吧?您可以建议任何替代方法来完成此操作?
    • 实际上,我想从配置文件中获取这些地图。我不确定使用宏是否会起作用,因为我希望这些特征以后可以扩展(在此代码库之外),并且只需在配置文件中提供地图,并提供扩展这些特征的对象即可。跨度>
    • 即使 JVM 没有擦除类型,类型参数仍然需要在编译时确定以进行类型检查。
    • 谢谢。现在,我明白你的意思了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 2021-01-03
    • 2020-08-13
    • 2019-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多