2012-12-25 4 views
14

Diciamo un ho una classe:Come accedere ai valori dei parametri predefiniti tramite Scala reflection?

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

Sia il costruttore e il metodo applica in oggetto associato generato automaticamente prende tre parametri. Una volta osservato attraverso la riflessione, il terzo parametro (nota) è in posizione:

p.isParamWithDefault = true 

Inoltre, mediante ispezione posso trovare il metodo che produce il valore nell'oggetto compagna:

method <init>$default$3 

e

method apply$default$3 

che hanno entrambi anche:

m.isParamWithDefault = true 

Tuttavia, non riesco a trovare nulla sul TermSymbol per il parametro notes che in realtà punta ai metodi giusti per ottenere il valore predefinito né alcunché sui MethodSymbols sopra riportati che puntano al TermSymbol per il parametro.

C'è un modo semplice per collegare TermSymbol per il parametro con il metodo che genera il suo valore predefinito? O devo fare qualcosa di kludgey come ispezionare i nomi dei metodi sull'oggetto compagno?

Sono interessato a questo sia per l'esempio di costruttore case case che ho qui e per i metodi regolari.

+0

Ci sono gradi di kludge. codice di esempio in [1] [1] [questa risposta.]: Http://stackoverflow.com/a/13813000/1296806 –

+0

Sì, ho codificato qualcosa di simile a questo. Ma si basa sullo schema di mangling di Scala che * dovrebbe * essere trattato come un dettaglio di implementazione. In effetti c'è un thread su di esso in corso in questo momento: https://groups.google.com/d/topic/scala-internals/aE81MVdIhCk/discussion –

+0

Sì, anche seguendo il thread; il mio interesse derivava da un PR che aveva bisogno di passare da argomenti di default al metodo, ecc., è disordinato per via interiore, figuriamoci esternamente. Ma come notato, la forma del mangano è spec'd. –

risposta

9

Ci sono gradi di kludge.

Codice di esempio this answer, incollato di seguito.

Così come stavo dicendo, la forma del nome è nella specifica al 4.6, 6.6.1. Questo non è ad-hoc. For every parameter pi , j with a default argument a method named f $default$n is generated which computes the default argument expression.

La mancanza di capacità strutturate di accedere e ricostituire questi nomi generati è un problema noto (con un thread corrente sull'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

Grazie per aver aggiunto i riferimenti a SLS. Quando l'ho controllato l'avevo perso. Speravo in qualcosa di più pulito, ma almeno sembra dipendere da interfacce pubbliche e convenzioni specifiche. –

+2

Abbiamo anche https://issues.scala-lang.org/browse/SI-6468 –

+0

Si dovrebbe confrontare 'TypeTag's con l'operatore' =: = 'invece di solo' == ', quando si combina su' p.typeSignature' (vedi http://stackoverflow.com/a/12232195/3714539, attorno a 'res30' /' res31') – al3xar

3

È possibile fare questo senza fare ipotesi circa i nomi generati-lanciando alla API interna:

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 

Naturalmente, in un modo in cui questo non è più gentile, dato che il nome il mangling è specificato e l'API interna non lo è, ma potrebbe essere più conveniente.

+0

La correzione della codifica per il costruttore era a mio avviso. Notare l'anomalia: 'scala> nme.defaultGetterName (nme.MIXIN_CONSTRUCTOR, 3)' è 'res0: $ r.intp.global.TermName = $ lessinit $ greater $ default $ 3'. Per anomalia intendo bug se ha importanza. Quindi, discutibilmente, è meglio usare l'API dipendente dall'implementazione, che preserva l'anomalia. –