【问题标题】:How do I access default parameter values via Scala reflection?如何通过 Scala 反射访问默认参数值?
【发布时间】:2012-12-25 21:32:57
【问题描述】:

假设我有一门课:

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

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

p.isParamWithDefault = true

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

method <init>$default$3

method apply$default$3

两者都有:

m.isParamWithDefault = true

但是,我无法在 notes 参数的 TermSymbol 上找到任何实际指向正确方法以获取默认值的内容,也无法在上述 MethodSymbols 上找到任何指向该参数的 TermSymbol 的内容。

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

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

【问题讨论】:

  • 有一定程度的混搭。 [this answer.][1] [1] 处的示例代码:stackoverflow.com/a/13813000/1296806
  • 是的,我编写了类似的代码。但它依赖于 Scala 的名称修饰方案,应该被视为实现细节。事实上,现在有一个线程正在进行:groups.google.com/d/topic/scala-internals/aE81MVdIhCk/…
  • 是的,也在跟帖;我的兴趣源于需要从默认参数到方法等的 PR,内部很混乱,更不用说外部了。但如前所述,mangle 的形式是指定的。
  • 如果我理解正确,默认参数方法的名称修改是在命名和默认参数的 SID 中指定的,但它不在 SLS 中。我不确定,但我不认为 SID 被视为实现必须符合的硬规范。它们是更通用的设计文档。
  • 我的答案编辑包括两个 SLS 位置。你会认为这将是 impl 细节。我的用例是,我创建了一个带有可选消息的愚蠢异常类,我不想为默认 arg 生成一个伴侣。另一种方法是重载构造函数,就像你通常手动做的那样。

标签: scala reflection scala-2.10


【解决方案1】:

有一定程度的混搭。

this answer 的示例代码,粘贴在下方。

正如我所说,名称的形式在 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]())
}

【讨论】:

  • 感谢您添加对 SLS 的引用。当我检查它时,我错过了它。我希望有一些更干净的东西,但至少它似乎依赖于公共接口和指定的约定。
  • 当您匹配p.typeSignature 时,您应该将TypeTags 与=:= 运算符进行比较,而不仅仅是==(请参阅stackoverflow.com/a/12232195/3714539,在res30/res31 附近)
  • 是否可以使用编译时反射(即从宏内部)获取默认值?
  • @josephpconley 方法是在命名器阶段早期添加的,所以,是的。
  • 您从im reflectMethod 获得的MethodMirror 是运行时镜像,是否有其他方法可以在编译时调用MethodSymbol
【解决方案2】:

可以做到这一点,而无需对生成的名称做出假设——通过转换为内部 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,但它可能更方便。

【讨论】:

  • 修复构造函数的编码是我做的。注意异常:scala&gt; nme.defaultGetterName(nme.MIXIN_CONSTRUCTOR, 3)res0: $r.intp.global.TermName = $lessinit$greater$default$3。通过异常,我的意思是如果它重要的话。因此,可以说,最好使用依赖于实现的、保持异常的 API。
【解决方案3】:

对于只有TypeFoo 的情况:

% 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))

您可以调用伴生对象上的任何方法。

Documentation about Mirrors 包含如何在类上调用方法的示例。

【讨论】:

    猜你喜欢
    • 2011-02-09
    • 2012-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多