斯卡拉反思难题:你能解释一下这些奇怪的结果吗?

时间:2014-11-06 19:03:14

标签: scala reflection

我使用反射编写了一些Scala代码,它返回某个对象中所有类型的val。以下是此代码的三个版本。其中一个有效,但很难看。改进它的两次尝试以不同的方式起作用。你能解释一下原因吗?

首先,代码:

import scala.reflect.runtime._
import scala.util.Try

trait ScopeBase[T] {
  // this version tries to generalize the type. The only difference
  // from the working version is [T] instead of [String]
  def enumerateBase[S: universe.TypeTag]: Seq[T] = {
    val mirror = currentMirror.reflect(this)
    universe.typeOf[S].decls.map {
      decl => Try(mirror.reflectField(decl.asMethod).get.asInstanceOf[T])
    }.filter(_.isSuccess).map(_.get).filter(_ != null).toSeq
  }
}

trait ScopeString extends ScopeBase[String] {
  // This version works but requires passing the val type
  // (String, in this example) explicitly. I don't want to
  // duplicate the code for different val types.
  def enumerate[S: universe.TypeTag]: Seq[String] = {
    val mirror = currentMirror.reflect(this)
    universe.typeOf[S].decls.map {
      decl => Try(mirror.reflectField(decl.asMethod).get.asInstanceOf[String])
    }.filter(_.isSuccess).map(_.get).filter(_ != null).toSeq
  }

  // This version tries to avoid passing the object's type
  // as the [S] type parameter. After all, the method is called
  // on the object itself; so why pass the type?
  def enumerateThis: Seq[String] = {
    val mirror = currentMirror.reflect(this)
    universe.typeOf[this.type].decls.map {
      decl => Try(mirror.reflectField(decl.asMethod).get.asInstanceOf[String])
    }.filter(_.isSuccess).map(_.get).filter(_ != null).toSeq
  }
}

// The working example
object Test1 extends ScopeString {
  val IntField: Int = 13
  val StringField: String = "test"
  lazy val fields = enumerate[Test1.type]
}

// This shows how the attempt to generalize the type doesn't work
object Test2 extends ScopeString {
  val IntField: Int = 13
  val StringField: String = "test"
  lazy val fields = enumerateBase[Test2.type]
}

// This shows how the attempt to drop the object's type doesn't work
object Test3 extends ScopeString {
  val IntField: Int = 13
  val StringField: String = "test"
  lazy val fields = enumerateThis
}

val test1 = Test1.fields // List(test)
val test2 = Test2.fields // List(13, test)
val test3 = Test3.fields // List()

“枚举”方法确实有效。但是,正如您在Test1示例中所看到的,它需要将对象自己的类型(Test1.type)作为参数传递,这不应该是必需的。 “enumerateThis”方法试图避免这种情况但失败,产生一个空列表。 “enumerateBase”方法尝试通过将val类型作为参数传递来概括“枚举”代码。但它也失败了,产生了所有val的列表,而不仅仅是某种类型的列表。

知道发生了什么事吗?

3 个答案:

答案 0 :(得分:2)

你的通用实现中的问题是丢失了T的类型信息。另外,不要使用异常作为控制逻辑的主要方法(它非常慢!)。这是你的基础的工作版本。

abstract class ScopeBase[T : universe.TypeTag, S <: ScopeBase[T, S] : universe.TypeTag : scala.reflect.ClassTag] {
  self: S =>

  def enumerateBase: Seq[T] = {
    val mirror = currentMirror.reflect(this)
    universe.typeOf[S].baseClasses.map(_.asType.toType).flatMap(
      _.decls
        .filter(_.typeSignature.resultType <:< universe.typeOf[T])
        .filter(_.isMethod)
        .map(_.asMethod)
        .filter(_.isAccessor)
        .map(decl => mirror.reflectMethod(decl).apply().asInstanceOf[T])
        .filter(_ != null)
    ).toSeq
  }
}

trait Inherit {
  val StringField2: String = "test2"
}

class Test1 extends ScopeBase[String, Test1] with Inherit {
  val IntField: Int = 13
  val StringField: String = "test"
  lazy val fields = enumerateBase
}

object Test extends App {
  println(new Test1().fields)
}

答案 1 :(得分:1)

不是从universe.typeOf获取类型,而是可以使用运行时类currentMirror.classSymbol(getClass).toType,下面是一个有效的示例:

def enumerateThis: Seq[String] = {
  val mirror = currentMirror.reflect(this)
  currentMirror.classSymbol(getClass).toType.decls.map {
    decl => Try(mirror.reflectField(decl.asMethod).get.asInstanceOf[String])
  }.filter(_.isSuccess).map(_.get).filter(_ != null).toSeq
}

//prints List(test)

答案 2 :(得分:1)

在大家的帮助下,这是有效的最终版本:

import scala.reflect.runtime.{currentMirror, universe}

abstract class ScopeBase[T: universe.TypeTag] {
  lazy val enumerate: Seq[T] = {
    val mirror = currentMirror.reflect(this)
    currentMirror.classSymbol(getClass).baseClasses.map(_.asType.toType).flatMap {
      _.decls
        .filter(_.typeSignature.resultType <:< universe.typeOf[T])
        .filter(_.isMethod)
        .map(_.asMethod)
        .filterNot(_.isConstructor)
        .filter(_.paramLists.size == 0)
        .map(decl => mirror.reflectField(decl.asMethod).get.asInstanceOf[T])
        .filter(_ != null).toSeq
    }
  }
}

trait FieldScope extends ScopeBase[Field[_]]
trait DbFieldScope extends ScopeBase[DbField[_, _]] {
  // etc....
}

从最后几行可以看出,我的用例仅限于特定字段类型的范围对象。这就是我想要对范围容器进行参数化的原因。如果我想在单个范围容器中枚举多个类型的字段,那么我将参数化枚举方法。