2016-07-14 3 views
0

Ich frage mich, ob es eine Möglichkeit gibt, die statischen Methoden automatisch von einem Begleitobjekt zu übernehmen, sodass Sie sie über die Instanz referenzieren können. Wie:Objektmethoden in eine Klasse hieven

object Foo { 
    def bar = 5 
} 
case class Foo() 

val f = Foo() 
f.bar 

Extra-Bonuspunkte für einen Weg, um automatisch objekt Methoden zu erheben, die eine Instanz der Klasse erfordern, während der Instanz Parameter zu entfernen. (Übernimmt this)

object Foo { 
    def bar(f: Foo) = ... 
} 
case class Foo() 

val f = Foo() 
f.bar // instead of Foo.bar(f) 

Antwort

1

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 ...