2016-03-27 11 views
2

Angenommen, ich habe eine Eigenschaft Foo mit mehreren Methoden. Ich möchte ein neues Merkmal erstellen, das Foo erweitert, aber jeden Methodenaufruf "umschließt", zum Beispiel mit einer Druckanweisung (in Wirklichkeit wird das etwas komplizierter sein/ich habe ein paar verschiedene Anwendungsfälle im Sinn).Mixin, um jede Methode einer Scala-Eigenschaft zu wickeln

trait Foo { 
    def bar(x: Int) = 2 * x 
    def baz(y: Int) = 3 * y 
} 

Ich kann dies manuell tun, indem jede Methode überschrieben. Aber das scheint unnötig ausführliche (und nur allzu leicht die falsche Super-Methode zu nennen):

object FooWrapped extends FooWrapped 
trait FooWrapped extends Foo { 
    override def bar(x: Int) ={ 
    println("call") 
    super.bar(x) 
    } 
    override def baz(y: Int) ={ 
    println("call") 
    super.baz(y) 
    } 
} 

scala> FooWrapped.bar(3) 
call 
res3: Int = 6 

Ich habe gehofft, einen mixin Zug zu schreiben, dass ich mit anderen Zügen wieder zu verwenden wäre in der Lage, und die Verwendung als:

So muss ich nicht manuell jede Methode überschreiben (die Mixin würde dies für mich tun).

Ist es möglich, eine solche Mixin-Eigenschaft in Scala zu schreiben? Wie würde es aussehen?

+0

Es sollte unter Verwendung der Makro Anmerkungen machbar sein (http: //docs.scala-lang .org/Übersichten/Makros/Anmerkungen), denke ich. Dies ist ein Kommentar, keine Antwort, weil ich das Makro im Moment nicht schreiben kann. –

+0

Sie können das stapelbare Merkmalmuster auch verwenden, um ein bestimmtes Verhalten an vorhandene Merkmale anzuhängen, sodass Ihre Aufrufe wie folgt aussehen: val a = new SomeClass mit PrintCall mit Logger mit Whatever. Sie injizieren einfach die Verhaltensweisen in der Reihenfolge, in der sie ausgeführt werden sollen (im Fall von Drucken und Protokollieren ist dies egal, aber wenn Ihre Merkmale AddTwo und MultiplyByThree sind, ist die Reihenfolge wichtig). Ich schreibe im Kommentar, da es nicht wirklich das ist, wonach du gefragt hast, aber wenn du mit diesem Muster nicht vertraut bist, schau es dir an, es kann ziemlich cool sein (du musst jedoch jede Methode explizit überschreiben). – slouc

+0

@slouc Ich hoffe auf eine Lösung, die nicht bedeutet, dass ich "Override Def Bar" manuell für jede Methode schreiben muss. Hauptsächlich, weil das bedeutet, dass ich das nicht allgemein schreiben kann (ohne vorher alle Methoden und Signaturen zu kennen). –

Antwort

2

Aktualisieren Hier ist das Makro. Es war viel weniger schmerzhaft als ich dachte, dass es wegen Quasiquotes sein würde. Sie sind großartig. Dieser Code macht nur wenig und Sie müssen ihn wahrscheinlich verbessern. Es kann einige spezielle Situationen nicht berücksichtigen. Es geht auch davon aus, dass weder die Elternklasse noch die Methode params enthält, sondern nur die Methoden der angegebenen Klasse oder Eigenschaft, aber nicht die Elternmethoden, es funktioniert möglicherweise nicht, wenn Sie Hilfskonstruktoren usw. haben. Trotzdem hoffe ich, dass es Ihnen helfen wird Eine Idee, wie das für Ihre speziellen Bedürfnisse zu tun ist, so dass es für alle Situationen funktioniert, ist für mich momentan zu groß.

object MacrosLogging { 
    import scala.language.experimental.macros 
    import scala.reflect.macros.blackbox 


    def log_wrap[T](): T = macro log_impl[T] 
    def log_impl[T : c.WeakTypeTag](c: blackbox.Context)(): c.Expr[T] = { 
    import c.universe._ 

    val baseType = implicitly[c.WeakTypeTag[T]].tpe 
    val body = for { 
     member <- baseType.declarations if member.isMethod && member.name.decodedName.toString != "$init$" 
     method = member.asMethod 
     params = for {sym <- method.paramLists.flatten} yield q"""${sym.asTerm.name}: ${sym.typeSignature}""" 
     paramsCall = for {sym <- method.paramLists.flatten} yield sym.name 
     methodName = member.asTerm.name.toString 
    } yield { 
     q"""override def ${method.name}(..$params): ${method.returnType} = { println("Method " + $methodName + " was called"); super.${method.name}(..$paramsCall); }""" 
    } 

    c.Expr[T] {q""" { class A extends $baseType { ..$body }; new A } """} 
    } 
} 

Wenn Sie nicht wollen, eine Instanz erstellen, aber Sie möchten die Protokollierung für Ihre Eigenschaft nur hinzufügen, damit Sie weiter mixin könnte man dies mit relativ gleichen Code zu tun, aber Makro Paradies Typenannotationen mit : http://docs.scala-lang.org/overviews/macros/annotations Diese können Sie Ihre Klassendefinitionen markieren und Änderungen durchführen direkt in den Definitionen


Sie etwas tun könnte, wie Sie mit Dynamic wollen, aber es gibt einen Haken - man kann es nicht von Original-Typ machen, also ist es kein Mixin. Dynamische Funktionen funktionieren nur, wenn die Typprüfung fehlschlägt. Sie können also den realen Typ nicht mischen (oder ich weiß nicht, wie ich das machen soll). Die wirkliche Antwort würde wahrscheinlich Makros erfordern (wie @AlexeyRomanov in Kommentaren vorgeschlagen), aber ich bin mir nicht sicher, wie man einen schreibt, vielleicht werde ich später darauf kommen. Noch Dynamic könnte für Sie arbeiten, wenn Sie nicht für DI hier auf der Suche sind

trait Foo { 
    def bar(x: Int) = 2 * x 
    def baz(y: Int) = 3 * y 
    } 
    import scala.reflect.runtime.{universe => ru} 
    import scala.language.dynamics 

    trait Wrapper[T] extends Dynamic { 
    val inner: T 
    def applyDynamic(name: String)(args: Any*)(implicit tt: ru.TypeTag[T], ct: ClassTag[T]) = { 
     val im = tt.mirror.reflect(inner) 
     val method = tt.tpe.decl(ru.TermName(name)).asMethod 
     println(method) 
     val mm = im.reflectMethod(method) 
     println(s"$name was called with $args") 
     mm.apply(args:_*) 
    } 
    } 

    class W extends Wrapper[Foo] { 
    override val inner: Foo = new Foo() {} 
    } 

    val w = new W // Cannot be casted to Foo 
    println(w.bar(5)) // Logs a call and then returns 10 

Sie mehr über Dynamic hier lesen: https://github.com/scala/scala/blob/2.12.x/src/library/scala/Dynamic.scala

+0

Interessant! Es ist ein wenig seltsam für mich, dass dies dem Compiler nicht erlaubt (Methodenfehler zu fangen) Vergleiche: 'Foo.bar()' und 'w.bar()' oder 'Foo.notfound' und' w.notfound'. Ich denke, das bedeutet, dass diese Lösung mir nicht helfen wird. +1 sowieso (ich wusste nicht, dass du das tun könntest). –

+0

@AndyHayden siehe Update. Es erfordert einige zusätzliche Arbeit, aber ich hoffe, es ist ein Ausgangspunkt für Sie – Archeg

Verwandte Themen