Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I access default parameter values via Scala reflection?

Let's say a I have a class:

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

Both the constructor and the apply method in the automatically generated companion object take three parameters. When viewed via reflection, the third parameter (note) is flagged:

p.isParamWithDefault = true

Also, by inspection I can find the method that produces the value in the companion object:

method <init>$default$3

and

method apply$default$3

Which both also have:

m.isParamWithDefault = true

However, I can neither find anything on the TermSymbol for the notes parameter that actually points me at the right methods to obtain the default value nor anything on the above MethodSymbols that point back to the TermSymbol for the parameter.

Is there a straight forward way to link TermSymbol for the parameter with the method that generates its default value? Or do I need to do something kludgey like inspect the names of the methods on the companion object?

I'm interested in this both for the case class constructor example I have here and for regular methods.

like image 777
Erik Engbrecht Avatar asked Dec 25 '12 21:12

Erik Engbrecht


People also ask

Where do default parameters appear?

Default parameter values must appear on the declaration, since that is the only thing that the caller sees.

How do we provide the default values of function parameters?

In C++ programming, we can provide default values for function parameters. If a function with default arguments is called without passing arguments, then the default parameters are used. However, if arguments are passed while calling the function, the default arguments are ignored.

What is the function parameter with a default value in Scala?

Scala provides the ability to give parameters default values that can be used to allow a caller to omit those parameters. The parameter level has a default value so it is optional. On the last line, the argument "WARNING" overrides the default argument "INFO" .

Can parameters have default values?

In JavaScript, a parameter has a default value of undefined. It means that if you don't pass the arguments into the function, its parameters will have the default values of undefined .


2 Answers

There are degrees of kludge.

Sample code at this answer, pasted below.

So as I was saying, the form of the name is in the spec at 4.6, 6.6.1. That is not 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.

The lack of structured ability to access and reconstitute these generated names is a known issue (with a current thread on the 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]())
}
like image 170
som-snytt Avatar answered Sep 21 '22 12:09

som-snytt


You can do this without making assumptions about the generated names—by casting to the internal 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

Of course in a way this isn't any nicer, since the name mangling is specified and the internal API isn't, but it may be more convenient.

like image 25
Travis Brown Avatar answered Sep 21 '22 12:09

Travis Brown