19

假设我有一堂课:

case class Foo(id: Int, name: String, note: Option[String] = None)

自动生成的伴生对象中的构造函数和应用方法都采用三个参数。当通过反射查看时,第三个参数(注)被标记:

p.isParamWithDefault = true

此外,通过检查,我可以找到在伴随对象中产生值的方法:

method <init>$default$3

method apply$default$3

两者都有:

m.isParamWithDefault = true

但是,我在 TermSymbol 上找不到任何关于 notes 参数的东西,它实际上将我指向正确的方法来获取默认值,也找不到在上述 MethodSymbols 上指向参数的 TermSymbol 的任何东西。

是否有直接的方法将参数的 TermSymbol 与生成其默认值的方法联系起来?还是我需要做一些笨拙的事情,比如检查伴随对象上的方法名称?

我对这里的案例类构造函数示例和常规方法都感兴趣。

4

3 回答 3

9

有一定程度的杂乱无章。

此答案的示例代码,粘贴在下面。

正如我所说,名称的形式在 4.6、6.6.1 的规范中。这不是临时的。For every parameter pi , j with a default argument a method named f $default$n is generated which computes the default argument expression.

缺乏访问和重构这些生成名称的结构化能力是一个已知问题(ML 上有一个当前线程)。

import reflect._
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._

// case class instance with default args

// Persons entering this site must be 18 or older, so assume that
case class Person(name: String, age: Int = 18) {
  require(age >= 18)
}

object Test extends App {

  // Person may have some default args, or not.
  // normally, must Person(name = "Guy")
  // we will Person(null, 18)
  def newCase[A]()(implicit t: ClassTag[A]): A = {
    val claas = cm classSymbol t.runtimeClass
    val modul = claas.companionSymbol.asModule
    val im = cm reflect (cm reflectModule modul).instance
    defaut[A](im, "apply")
  }

  def defaut[A](im: InstanceMirror, name: String): A = {
    val at = newTermName(name)
    val ts = im.symbol.typeSignature
    val method = (ts member at).asMethod

    // either defarg or default val for type of p
    def valueFor(p: Symbol, i: Int): Any = {
      val defarg = ts member newTermName(s"$name$$default$$${i+1}")
      if (defarg != NoSymbol) {
        println(s"default $defarg")
        (im reflectMethod defarg.asMethod)()
      } else {
        println(s"def val for $p")
        p.typeSignature match {
          case t if t =:= typeOf[String] => null
          case t if t =:= typeOf[Int]    => 0
          case x                         => throw new IllegalArgumentException(x.toString)
        }
      }
    }
    val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
    (im reflectMethod method)(args: _*).asInstanceOf[A]
  }

  assert(Person(name = null) == newCase[Person]())
}
于 2012-12-25T23:51:15.973 回答
4

可以通过强制转换为内部 API 来做到这一点,而无需对生成的名称做出假设:

scala> :power
** Power User mode enabled - BEEP WHIR GYVE **
** :phase has been set to 'typer'.          **
** scala.tools.nsc._ has been imported      **
** global._, definitions._ also imported    **
** Try  :help, :vals, power.<tab>           **

scala> case class Foo(id: Int, name: String, note: Option[String] = None)
defined class Foo

scala> val t = typeOf[Foo.type]
t: $r.intp.global.Type = Foo.type

scala> t.declaration(nme.defaultGetterName(nme.CONSTRUCTOR, 3))
res0: $r.intp.global.Symbol = method <init>$default$3

scala> t.declaration(nme.defaultGetterName(newTermName("apply"), 3))
res1: $r.intp.global.Symbol = method apply$default$3

当然,这在某种程度上也不是更好,因为指定了名称修饰而没有指定内部 API,但它可能更方便。

于 2012-12-26T00:00:49.083 回答
1

对于只有以下情况Type的情况Foo

% scala
Welcome to Scala 2.13.1 (OpenJDK 64-Bit Server VM, Java 15-loom).

scala> :paste

import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe.definitions._

def defaults(tpe: Type): Seq[(Int, Any)] = {
  tpe.typeSymbol.asClass.primaryConstructor.asMethod.paramLists.flatten.
    zipWithIndex.flatMap{ case (x, i) =>
    if (x.asTerm.isParamWithDefault) {
      val m = currentMirror
      val im = m.reflect(
        m.reflectModule(tpe.typeSymbol.asClass.companion.asModule).instance
      )
      val method = tpe.companion.decl(
        TermName("apply$default$"+(i+1).toString)
      ).asMethod
      val v = im.reflectMethod(method)()
      Some(i -> v)
    } else None
  }
}

scala> case class Foo(id: Int, name: String, note: Option[String] = None)

scala> defaults(typeOf[Foo])
res0: Seq[(Int, Any)] = List((2,None))

与您可以在伴随对象上调用任何方法的方式相同。

有关 Mirrors 的文档包含如何在类上调用方法的示例。

于 2020-04-03T09:50:15.970 回答