2012-12-25 38 views
14

比方說一個我有一個類:如何通過Scala反射訪問默認參數值?

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上找到筆記參數的任何內容,這些參數實際上指向正確的方法以獲取默認值,也沒有指出上述MethodSymbols上指向參數TermSymbol的任何內容。

是否有直接的方法將TermSymbol鏈接到生成默認值的方法?還是我需要做一些kludgey,比如檢查伴侶對象上方法的名稱?

我感興趣的是這兩個案例類構造函數示例我有這裏和常規方法。

+0

有kludge程度。在 示例代碼[1] [1] [這個答案]:http://stackoverflow.com/a/13813000/1296806 –

+0

是啊,我編碼類似的東西。但它依賴於Scala的名稱修改方案,它應該被視爲實現細節。實際上現在有一個線程正在進行:https://groups.google.com/d/topic/scala-internals/aE81MVdIhCk/discussion –

+0

是的,也遵循線程;我的興趣來源於一個需要從默認參數到方法等的公共關係,這是凌亂的,更不用說在外部了。但是如前所述,破損的形式是特定的。 –

回答

9

有kludge程度。

樣本代碼爲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]()) 
} 
+0

感謝您將參考添加到SLS。當我檢查它時,我錯過了它。我希望有更清潔的東西,但至少看起來依賴於公共接口和指定的約定。 –

+2

我們也有https://issues.scala-lang.org/browse/SI-6468 –

+0

當你在''='='運算符而不是'=='匹配時,你應該比較'TypeTag's' p.typeSignature'(參見http://stackoverflow.com/a/12232195/3714539,在'res30' /'res31'附近) – al3xar

3

可以做到這一點沒有做有關生成的假設名稱,通過轉換成內部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 

當然,在某種程度上,這是沒有什麼更好的,因爲名字指定了mangling並且內部API不是,但它可能更方便。

+0

修復構造函數的編碼是我的事。注意這個異常:'scala> nme.defaultGetterName(nme.MIXIN_CONSTRUCTOR,3)''是'res0:$ r.intp.global.TermName = $ lessinit $ greater $ default $ 3'。通過異常,我的意思是錯誤,如果它重要。所以可以說,最好使用依賴於實現的異常保留API。 –