在 Scala 中,如何以编程方式确定案例类的字段名称? [英] In Scala, how can I programmatically determine the name of the fields of a case class?

查看:41
本文介绍了在 Scala 中,如何以编程方式确定案例类的字段名称?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Scala 中,假设我有一个这样的案例类:

In Scala, suppose I have a case class like this:

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

有没有办法让我获得 Seq[(String, Class[_])],或者更好的是,Seq[(String, Manifest)],描述案例类的参数?

Is there a way for me to obtain a Seq[(String, Class[_])], or better yet, Seq[(String, Manifest)], describing the case class's parameters?

推荐答案

又是我(两年后).这是使用 Scala 反射的不同解决方案.它的灵感来自 博客文章,其灵感来自于堆栈溢出交换.下面的解决方案专门针对上面原发帖者的问题.

It's me again (two years later). Here's a different, different solution using Scala reflection. It is inspired by a blog post, which was itself inspired by a Stack Overflow exchange. The solution below is specialized to the original poster's question above.

在一个编译单元(REPL :paste 或编译的 JAR)中,包含 scala-reflect 作为依赖项并编译以下内容(在 Scala 2.11 中测试,可能在 Scala 2.10 中工作):

In one compilation unit (a REPL :paste or a compiled JAR), include scala-reflect as a dependency and compile the following (tested in Scala 2.11, might work in 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 中的下一行或使用前一行编译的代码作为依赖项)中,像这样使用它:

And in another compilation unit (the next line in the REPL or code compiled with the previous as a dependency), use it like this:

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)

这似乎有点矫枉过正,但我​​一直没能把它缩短.这是它的作用:

It seems like overkill, but I haven't been able to get it any shorter. Here's what it does:

  1. caseClassFields 函数创建了一个中间 CaseClassFieldsExtractor,它隐式存在,报告其发现,然后消失.
  2. CaseClassFieldsExtractor 是一个带有伴随对象的特征,该对象使用宏定义了该特征的匿名具体子类.宏可以检查您的案例类的字段,因为它具有关于案例类的丰富的编译器级信息.
  3. CaseClassFieldsExtractor 及其伴随对象必须在前一个编译单元中声明给检查您的 case 类的编译单元,以便宏在您想要使用它的时候存在.
  4. 案例类的类型数据通过WeakTypeTag 传入.这评估为具有大量模式匹配的 Scala 结构,但我找不到任何文档.
  5. 我们再次假设只有一个(主"?)构造函数,但我认为 Scala 中定义的所有类只能有一个构造函数.由于此技术检查构造函数的字段,而不是类中的所有 JVM 字段,因此它不会受到破坏我之前解决方案的缺乏通用性的影响.
  6. 它使用 quasiquotes 来构建 CaseClassFieldsExtractor 的匿名、具体子类.
  7. 所有这些隐式"业务都允许在函数调用 (caseClassFields) 中定义和包装宏,而不会在尚未定义时过早调用.
  1. The caseClassFields function creates an intermediate CaseClassFieldsExtractor that implicitly comes into existence, reports its findings, and disappears.
  2. The CaseClassFieldsExtractor is a trait with a companion object that defines an anonymous concrete subclass of this trait, using a macro. It is the macro that can inspect your case class's fields because it has rich, compiler-level information about the case class.
  3. The CaseClassFieldsExtractor and its companion object must be declared in a previous compilation unit to the one that examines your case class so that the macro exists at the time you want to use it.
  4. Your case class's type data is passed in through the WeakTypeTag. This evaluates to a Scala structure with lots of pattern matching and no documentation that I could find.
  5. We again assume that there's only one ("primary"?) constructor, but I think all classes defined in Scala can have only one constructor. Since this technique examines the fields of the constructor, not all JVM fields in the class, so it's not susceptible to the lack of generality that marred my previous solution.
  6. It uses quasiquotes to build up an anonymous, concrete subclass of the CaseClassFieldsExtractor.
  7. All that "implicit" business allows the macro to be defined and wrapped up in a function call (caseClassFields) without being called too early, when it's not yet defined.

欢迎提出任何可以改进此解决方案或解释隐含"究竟是如何做的(或者是否可以删除)的评论.

Any comments that could refine this solution or explain how exactly the "implicits" do what they do (or if they can be removed) are welcome.

这篇关于在 Scala 中,如何以编程方式确定案例类的字段名称?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆