2016-03-27 52 views
2

假设我有一个特征Foo与几种方法。我想创建一个扩展Foo的新特性,但是“包装”每个方法调用,例如用一些打印语句(实际上这会更复杂一些/我有几个不同的用例)。混合包装斯卡拉特质的每种方法

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

我可以通过重写每个方法手动执行此操作。但这似乎不必要的冗长(和太容易调用错误的超级方法):

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 

我希望写一个mixin特质,那我就可以和其他特点重用,并且使用:

trait FooWrapped extends Foo with PrintCall 

这样我就不必手动覆盖每个方法(mixin会为我做这个)。

是否有可能在Scala中编写这样一个mixin特性?它会是什么样子?

+0

它应该是可行的使用宏注释(HTTP://docs.scala-lang .org/overviews /宏/注释),我想。这是一个评论,而不是一个答案,因为我目前无法编写宏。 –

+0

您还可以使用可堆叠特征模式将特定行为附加到现有特征,以便您的调用看起来像:val a = New PrintCall的SomeClass和Logger with Whatever。您只需按照希望执行的顺序注入行为(如果打印和记录它并不重要,但是如果您的特征是AddTwo和MultiplyByThree,那么顺序很重要)。我在评论中写道,因为它不是真正你所要求的,但如果你不熟悉这种模式,请检查它,它可以非常酷(但你确实需要明确地覆盖每个方法)。 – slouc

+0

@slouc我希望有一个解决方案,并不意味着我必须手动为每种方法编写'override def bar'。主要是因为这样做意味着我不能一般性地写出这些(不提前知道所有方法和签名)。 –

回答

2

更新这是宏。由于quasiquotes,它比我想的要痛苦得多。他们很棒。这段代码只有一点点,你可能不得不改进它。它可能不会解释一些特殊情况。此外,它假定父类和它的方法都没有类型参数,它只包含给定类或特征的方法,但不包含父类方法,如果你有辅助构造函数等,它可能不起作用。不过,我希望它会给你关于如何为您的具体需求做到这一点的想法,使其适用于所有情况,不幸的是现在对我来说太大了。

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 } """} 
    } 
} 

如果你不想创建一个实例,但你想只为你的特质添加记录,以便你可以进一步混入,你可以用比较相同的代码做到这一点,但使用微距天堂类型注释:http://docs.scala-lang.org/overviews/macros/annotations这允许你标记你的类定义和执行权的定义


你可以不喜欢,你想Dynamic东西在里面修改,但有一个陷阱 - 你不能让它原始类型,所以它不是一个混合。只有在类型检查失败的情况下,动态才能开始工作,所以你不能混入真正的类型(或者我不知道该怎么做)。真正的答案可能需要宏(因为@AlexeyRomanov在评论中提出),但我不确定如何写一个宏,也许我会在稍后提出。不过Dynamic可能会为你工作,如果你是不是在找DI这里

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 

你可以阅读更多关于Dynamic这里:https://github.com/scala/scala/blob/2.12.x/src/library/scala/Dynamic.scala

+0

有趣!这对我来说有点奇怪,它不允许编译器(捕获方法错误)比较:'Foo.bar()'和'w.bar()'或'Foo.notfound'和'w.notfound'。我认为这意味着这个解决方案不会起作用。无论如何+1(我不知道你可以做到这一点)。 –

+0

@AndyHayden请参阅更新。它需要做一些额外的工作,但我希望这是你的一个起点 – Archeg