Working in a codebase with scala that wants you, for certain classes, to define a sort of "make a new version" - so for instance if you have a class x(a :int, b:String, c:double)... it would have a function like this:
class x( a: Integer, b : String, c : Double) extends CanMakeNew
{
def newValue() = x( a, b, c)
}
I have no control over that - but would prefer not to implement it every time. Or, well... ever. Is there a way in scala, with reflection - to iterate over the constructor parameter values? I can use reflection to look at the parameter types - but as parameternames has not been turned on for this module, and I can't turn it on - I can't correlate those with the stored values in the class. Fundamentally, I'm looking for anyway of implementing a trait like:
trait CanMakeNewDoneForMe extends CanMakeNew {
def newValue() {I need the code that goes here}
So does scala reflection have any way of either inspecting the constructor or inspecting the object and seeing "ahh, this was the third parameter in the constructor"?
If you make X
a case class it will have apply
, copy
... generated by compiler automatically.
And basically it’s not my codebase, so I can’t really change any of the shape of things...
When you make a class a case class you don't actually "change shape of things", you just add autogenerated methods.
Anyway, you can create a macro annotation that generates method newValue
.
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
class newValue extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro newValueMacro.impl
}
object newValueMacro {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
annottees match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
val tparams1 = tparams.map {
case q"$_ type $name[..$_] >: $_ <: $_" => tq"$name"
}
val paramss1 = paramss.map(_.map {
case q"$_ val $pat: $_ = $_" => pat
})
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
def newValue() = new $tpname[..$tparams1](...$paramss1)
..$stats
}
..$tail
"""
case _ => c.abort(c.enclosingPosition, "not a class")
}
}
}
@newValue
/*case*/ class X(a: Int, b : String, c : Double) {
override def toString: String = s"X($a, $b, $c)"
}
val x = new X(1, "a", 2.0) //X(1, a, 2.0)
// val x1 = x.copy()
val x1 = x.newValue() //X(1, a, 2.0)