Scalaで、次のようなケースクラスがあるとします。
case class Sample(myInt: Int, myString: String)
ケースクラスのパラメータを説明するSeq[(String, Class[_])]
、またはさらに良い方法を取得する方法はありますか?Seq[(String, Manifest)]
Scalaで、次のようなケースクラスがあるとします。
case class Sample(myInt: Int, myString: String)
ケースクラスのパラメータを説明するSeq[(String, Class[_])]
、またはさらに良い方法を取得する方法はありますか?Seq[(String, Manifest)]
私は基本的な解決策を提供するために自分の質問に答えていますが、代替案や改善点も探しています。
Javaと互換性があり、ケースクラスに制限されない1つのオプションは、ParaNamerを使用することです。Scalaでは、別のオプションは、ScalaSig
生成されたクラスファイルに添付されたバイトを解析することです。どちらのソリューションもREPLでは機能しません。
ScalaSig
これが(scalapとScala 2.8.1を使用する)からフィールドの名前を抽出する私の試みです:
def valNames[C: ClassManifest]: Seq[(String, Class[_])] = {
val cls = classManifest[C].erasure
val ctors = cls.getConstructors
assert(ctors.size == 1, "Class " + cls.getName + " should have only one constructor")
val sig = ScalaSigParser.parse(cls).getOrElse(error("No ScalaSig for class " + cls.getName + ", make sure it is a top-level case class"))
val classSymbol = sig.parseEntry(0).asInstanceOf[ClassSymbol]
assert(classSymbol.isCase, "Class " + cls.getName + " is not a case class")
val tableSize = sig.table.size
val ctorIndex = (1 until tableSize).find { i =>
sig.parseEntry(i) match {
case m @ MethodSymbol(SymbolInfo("<init>", owner, _, _, _, _), _) => owner match {
case sym: SymbolInfoSymbol if sym.index == 0 => true
case _ => false
}
case _ => false
}
}.getOrElse(error("Cannot find constructor entry in ScalaSig for class " + cls.getName))
val paramsListBuilder = List.newBuilder[String]
for (i <- (ctorIndex + 1) until tableSize) {
sig.parseEntry(i) match {
case MethodSymbol(SymbolInfo(name, owner, _, _, _, _), _) => owner match {
case sym: SymbolInfoSymbol if sym.index == ctorIndex => paramsListBuilder += name
case _ =>
}
case _ =>
}
}
paramsListBuilder.result zip ctors(0).getParameterTypes
}
免責事項:私はScalaSigの構造を本当に理解していないので、これはヒューリスティックと見なす必要があります。特に、このコードは次のことを前提としています。
ClassSymbol
です。MethodEntry
名前を持つ最初のコンストラクターです。<init>
ScalaSig
ネストされたケースクラスでは失敗します(noのため)。
このメソッドもClass
インスタンスのみを返し、 Manifest
sは返しません。
お気軽に改善をご提案ください!
プレーンJavaリフレクションを使用する別のソリューションを次に示します。
case class Test(unknown1: String, unknown2: Int)
val test = Test("one", 2)
val names = test.getClass.getDeclaredFields.map(_.getName)
// In this example, returns Array(unknown1, unknown2).
を取得するには、次のSeq[(String, Class[_])]
ようにします。
val typeMap = test.getClass.getDeclaredMethods.map({
x => (x.getName, x.getReturnType)
}).toMap[String, Class[_]]
val pairs = names.map(x => (x, typeMap(x)))
// In this example, returns Array((unknown1,class java.lang.String), (two,int))
入手方法がわかりませんManifests
。
再び私です(2年後)。これは、 Scalaリフレクションを使用した別の異なるソリューションです。それ自体がStackOverflow交換に触発されたブログ投稿に触発されています。以下の解決策は、上記の元の投稿者の質問に特化しています。
1つのコンパイルユニット(REPL:paste
またはコンパイルされたJAR)にscala-reflect
、依存関係としてインクルードし、以下をコンパイルします(Scala 2.11でテスト済み、Scala 2.10で動作する可能性があります)。
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object CaseClassFieldsExtractor {
implicit def makeExtractor[T]: CaseClassFieldsExtractor[T] =
macro makeExtractorImpl[T]
def makeExtractorImpl[T: c.WeakTypeTag](c: Context):
c.Expr[CaseClassFieldsExtractor[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if (m.isPrimaryConstructor) => m
}.get.paramLists.head
val extractParams = fields.map { field =>
val name = field.asTerm.name
val fieldName = name.decodedName.toString
val NullaryMethodType(fieldType) = tpe.decl(name).typeSignature
q"$fieldName -> ${fieldType.toString}"
}
c.Expr[CaseClassFieldsExtractor[T]](q"""
new CaseClassFieldsExtractor[$tpe] {
def get = Map(..$extractParams)
}
""")
}
}
trait CaseClassFieldsExtractor[T] {
def get: Map[String, String]
}
def caseClassFields[T : CaseClassFieldsExtractor] =
implicitly[CaseClassFieldsExtractor[T]].get
また、別のコンパイルユニット(REPLの次の行、または前の行を依存関係としてコンパイルしたコード)では、次のように使用します。
scala> case class Something(x: Int, y: Double, z: String)
defined class Something
scala> caseClassFields[Something]
res0: Map[String,String] = Map(x -> Int, y -> Double, z -> String)
やり過ぎのようですが、これ以上短くすることはできませんでした。これが何をするかです:
caseClassFields
中間体を作成します。CaseClassFieldsExtractor
CaseClassFieldsExtractor
、マクロを使用して、このトレイトの匿名の具象サブクラスを定義するコンパニオンオブジェクトを持つトレイトです。これは、ケースクラスに関する豊富なコンパイラレベルの情報を持っているため、ケースクラスのフィールドを検査できるマクロです。CaseClassFieldsExtractor
コンパニオンオブジェクトは、使用するときにマクロが存在するように、前のコンパイルユニットでケースクラスを調べるオブジェクトに宣言する必要があります。WeakTypeTag
。これは、多くのパターンマッチングがあり、ドキュメントが見つからないScala構造に評価されます。CaseClassFieldsExtractor
ます。caseClassFields
そのすべての「暗黙の」ビジネスにより、マクロがまだ定義されていないときに、早すぎる呼び出しをせずに、マクロを定義して関数呼び出し( )にまとめることができます。このソリューションを改良したり、「暗黙的」がどのように実行するか(または削除できるかどうか)を正確に説明できるコメントは歓迎されます。