9

Scalaで、次のようなケースクラスがあるとします。

case class Sample(myInt: Int, myString: String)

ケースクラスのパラメータを説明するSeq[(String, Class[_])]、またはさらに良い方法を取得する方法はありますか?Seq[(String, Manifest)]

4

3 に答える 3

7

私は基本的な解決策を提供するために自分の質問に答えていますが、代替案や改善点も探しています。


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の構造を本当に理解していないので、これはヒューリスティックと見なす必要があります。特に、このコードは次のことを前提としています。

  • ケースクラスにはコンストラクターが1つだけあります。
  • 位置0での署名の入力は常にClassSymbolです。
  • クラスの関連するコンストラクターは、所有者のIDが0のMethodEntry名前を持つ最初のコンストラクターです。<init>
  • パラメータ名には、所有者としてコンストラクタエントリがあり、常にそのエントリの後にあります。

ScalaSigネストされたケースクラスでは失敗します(noのため)。

このメソッドもClassインスタンスのみを返し、 Manifestsは返しません。

お気軽に改善をご提案ください!

于 2011-06-08T17:02:33.300 に答える
3

プレーン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

于 2013-08-26T05:33:43.063 に答える
2

再び私です(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)

やり過ぎのようですが、これ以上短くすることはできませんでした。これが何をするかです:

  1. この関数は、暗黙的に存在し、その結果を報告し、消えるcaseClassFields中間体を作成します。CaseClassFieldsExtractor
  2. CaseClassFieldsExtractor、マクロを使用して、このトレイトの匿名の具象サブクラスを定義するコンパニオンオブジェクトを持つトレイトです。これは、ケースクラスに関する豊富なコンパイラレベルの情報を持っているため、ケースクラスのフィールドを検査できるマクロです。
  3. とそのCaseClassFieldsExtractorコンパニオンオブジェクトは、使用するときにマクロが存在するように、前のコンパイルユニットでケースクラスを調べるオブジェクトに宣言する必要があります。
  4. ケースクラスの型データは、を介して渡されますWeakTypeTag。これは、多くのパターンマッチングがあり、ドキュメントが見つからないScala構造に評価されます。
  5. ここでも、(「プライマリ」?)コンストラクターは1つだけであると想定していますが、Scalaで定義されたすべてのクラスは1つのコンストラクターしか持てないと思います。この手法は、クラス内のすべてのJVMフィールドではなく、コンストラクターのフィールドを検査するため、以前のソリューションを損なう一般性の欠如の影響を受けません。
  6. 準引用符を使用して、の匿名の具体的なサブクラスを構築しCaseClassFieldsExtractorます。
  7. caseClassFieldsそのすべての「暗黙の」ビジネスにより、マクロがまだ定義されていないときに、早すぎる呼び出しをせずに、マクロを定義して関数呼び出し( )にまとめることができます。

このソリューションを改良したり、「暗黙的」がどのように実行するか(または削除できるかどうか)を正確に説明できるコメントは歓迎されます。

于 2016-02-10T02:00:04.263 に答える