2014-11-06 66 views
1

我写了一些使用反射的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类型作为参数来概括“枚举”代码。但它也失败了,产生了所有瓦尔的列表,而不仅仅是某种类型的列表。

任何想法是怎么回事?

+0

您是否尝试反编译类文件?这通常可以解释这些问题。 – bwawok 2014-11-06 19:07:34

+0

是的,我做到了。以上是裸骨的例子。原来有不同的文件甚至包的类。 – silverberry 2014-11-06 19:13:40

回答

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) 
+0

非常感谢,诺亚!这有很大帮助。现在我所有的问题都解决了。 :) – silverberry 2014-11-06 19:59:30

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) 
} 
+0

+1,良好的替代解决方案,照顾一些问题,'尝试'是相当恼人的工作,并缓慢... – Noah 2014-11-06 19:49:50

+0

非常感谢,Nate!我确实尝试将T定义为universe.TypeTag,但是如果没有对universe.typeOf [T]进行typeSignature比较,那么这种方法就无法工作。感谢您指出了这一点! 但是,我宁愿用[T:universe.TypeTag]参数化ScopeBase。当然,这个特质必须成为一个抽象类,但没关系。 任何方式摆脱[S],但? – silverberry 2014-11-06 19:57:13

+0

@silverberry我将我的答案更新为一个抽象类,它也捕获子类的类型信息。我不知道如何使它直接在一个对象上工作,但是你可以做'class T extends ScopeBase [String,T]',然后'object T extends T'。 – Nate 2014-11-06 20:26:50

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.... 
} 

当你从最后几行看,我的使用情况仅限于范围为特定的字段类型的对象。这就是为什么我要参数化范围容器。如果我想在一个范围容器中枚举多个类型的字段,那么我将参数化枚举方法。