如何通过Scala反射访问默认参数值?

如何通过Scala反射访问默认参数值?,scala,reflection,scala-2.10,Scala,Reflection,Scala 2.10,假设我有一门课: case class Foo(id: Int, name: String, note: Option[String] = None) 自动生成的伴生对象中的构造函数和apply方法都有三个参数。通过反射查看时,将标记第三个参数(注释): p.isParamWithDefault = true 此外,通过检查,我可以找到在伴生对象中产生值的方法: method <init>$default$3 两者都有: m.isParamWithDefault = true

假设我有一门课:

case class Foo(id: Int, name: String, note: Option[String] = None)
自动生成的伴生对象中的构造函数和apply方法都有三个参数。通过反射查看时,将标记第三个参数(注释):

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

method <init>$default$3
两者都有:

m.isParamWithDefault = true
但是,我在notes参数的TermSymbol上找不到任何实际指向正确方法以获取默认值的内容,在上述MethodSymbols上也找不到任何指向参数TermSymbol的内容

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


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

有一定程度的混乱

示例代码位于,粘贴在下面

正如我刚才所说,名称的形式在4.6,6.6.1的规范中。这不是临时的
对于每个带有默认参数的参数pi,j,将生成一个名为f$default$n的方法,该方法将计算默认参数表达式。

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

导入反射_
导入scala.reflect.runtime.{currentMirror=>cm}
导入scala.reflect.runtime.universe_
//带有默认参数的案例类实例
//进入本网站的人必须是18岁或以上,因此假设
案例类人员(姓名:字符串,年龄:Int=18){
要求(年龄>=18岁)
}
对象测试扩展应用程序{
//Person可能有一些默认参数,也可能没有。
//通常,必须是个人(name=“Guy”)
//我们将为一个人(空,18)
def newCase[A]()(隐式t:ClassTag[A]):A={
val claas=cm类符号t.runtimeClass
val module=claas.companysymbol.asModule
val im=cm reflect(cm reflect模块module)。实例
defaut[A](即时通讯,“应用”)
}
def defaut[A](im:InstanceMirror,名称:字符串):A={
val at=newTermName(名称)
val ts=im.symbol.typeSignature
val方法=(ts成员位于).asMethod
//p类型的默认值或默认值
def valueFor(p:Symbol,i:Int):任意={
val devarg=ts成员newTermName(s“$name$$default$$$${i+1}”)
if(defarg!=NoSymbol){
println(s“默认$devarg”)
(im reflectMethod defarg.asMethod)()
}否则{
println(s“定义价值为$p”)
p、 打字签名匹配{
如果t=:=typeOf[String]=>null,则为案例t
如果t=:=typeOf[Int]=>0,则为案例t
案例x=>抛出新的IllegalArgumentException(x.toString)
}
}
}
val args=(对于(ps),通过强制转换到内部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
scala>:电源
**电源用户模式已启用-哔哔声**
**:相位已设置为“typer”**
**scala.tools.nsc.\已导入**
**全局。\定义。\也已导入**
**尝试:帮助、:VAL、电源**
scala>case类Foo(id:Int,name:String,注意:Option[String]=None)
定义类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=方法$default$3
scala>t.declaration(nme.defaultGetterName(newTermName(“apply”),3))
res1:$r.intp.global.Symbol=方法应用$default$3

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

对于只有
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))
您可以用同样的方法调用同伴对象上的任何方法


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

存在一定程度的混乱。示例代码位于[此答案][1][1]:是的,我编写了类似的代码。但它依赖于Scala的名称篡改方案,这应该被视为一个实现细节。事实上,现在有一个线程正在运行:是的,也在线程之后;我的兴趣来自于一个PR,它需要从默认参数转到方法,等等,内部很混乱,更不用说ext了但是,正如前面提到的,mangle的形式是spec'd。如果我理解正确,默认参数的名称mangling方法在SID中为命名参数和默认参数指定,但在SLS中没有。我不确定,但我不相信SID被视为硬规范,实现必须遵守。它们更一般签署文档。我的答案编辑包括两个SLS LOC。你可能会认为这是impl detail。我的用例是,我创建了一个带有可选消息的哑异常类,我不想只为默认参数生成一个伴奏。替代方法是重载构造函数,就像你通常手工做的那样。修复构造函数的编码是我做的。注意异常:
scala>nme.defaultGetterName(nme.MIXIN_构造函数,3)
res0:$r.intp.global.TermName=$lessinit$greater$default$3
。所谓异常,我指的是bug,如果它重要的话。因此,可以说,最好使用依赖于实现的、保留异常的API。感谢您添加了对SLS的引用。当我检查它时,我错过了它。我希望有更干净的东西,但至少是a似乎依赖于公共接口和指定的约定。您应该将
TypeTag
s与
=:=
操作符进行比较,而不仅仅是
=
,当您在
p.typeSignature
上进行匹配时(请参见
res30
/
res31
)是否可以使用编译时反射获取默认值(即,从宏内部)?@josephpconley这些方法是在命名阶段提前添加的,因此,是的。
MethodMir
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
% 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))