Wenn Sie bereit sind, ein Compiler-Plugin zu akzeptieren macro paradise
(die hoffentlich mit der nächsten Version des Compilers ausgeliefert werden?), Können Sie eine Anmerkung machen, die alle tun, die Sie wollen. Insbesondere (ich denke, das ist, was Sie wollten):
- automatisch Objektmethoden hissen, die eine Instanz der Klasse als ihre ersten arg in Klassenmethoden erfordern (mit erstem arg =
this
)
- wird hissen verbleibende Objekt Methoden als Klassenmethoden (das ignoriert
this
)
Sie Sachen konfigurieren müssen, um Makros zu erstellen (oder this macro bare-bones repo nur klonen, den Makrocode unten mit ihm in Test.scala
in Macros.scala
und Experiment einfügen - sbt compile
kümmert sich um alles andere).
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros._
class liftFromObject extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro liftFromObjectMacro.impl
}
object liftFromObjectMacro {
def impl(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
annottees.map(_.tree) match {
case List(q"case class $cla(..$fields) extends ..$classBases { ..$classBody }"
,q"object $obj extends ..$objectBases { ..$objectBody }") =>
/* filter out from the object the functions we want to have in the class */
val newMethods = objectBody collect {
/* functions whose first arg indicates they are methods */
case q"def $name($arg: $t, ..$args) = { ..$body }"
if t.toString == cla.toString =>
q"def $name(..$args) = { val $arg = this; ..$body }"
/* other functions */
case [email protected]"def $name(..$args) = { ..$body }" => func
}
/* return the modified class and companion object */
c.Expr(q"""
case class $cla(..$fields) extends ..$classBases {
..$classBody;
..$newMethods
}
object $obj extends ..$objectBases { ..$objectBody }
""")
case _ => c.abort(c.enclosingPosition, "Invalid annottee")
}
}
}
Im Grunde alles, was Sie tun, ist mit Ast spielen und quasiquotes machen, die ziemlich einfach. Mit dem oben genannten konnte ich den Code unten ausführen und die gedruckte Ausgabe von Bar(3)
und 3
erhalten.
object Main extends App {
val t = Bar(1)
println(t.inc())
println(t.two)
}
object Bar {
def inc(b: Bar) = {
val Bar(i) = b; Bar(i+2)
}
def two() = 3
}
@liftFromObject
case class Bar(i: Int)
Beachten Sie, dass da dies nur AST Ebene Manipulation ist, kann es für die Erklärung, die gleichen Dinge auf unterschiedliche Weise ein wenig spröde bezüglich Scala verschiedenen Syntaxen sein ...