Mixin 来包装 Scala 特征的每个方法

And*_*den 5 scala decorator traits

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

trait Foo {
  def bar(x: Int) = 2 * x
  def baz(y: Int) = 3 * y
}
Run Code Online (Sandbox Code Playgroud)

我可以通过覆盖每个方法手动执行此操作。但这似乎不必要地冗长(而且很容易调用错误的超级方法):

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
Run Code Online (Sandbox Code Playgroud)

我希望编写一个混合特征,我将能够与其他特征重用,并用作:

trait FooWrapped extends Foo with PrintCall
Run Code Online (Sandbox Code Playgroud)

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

是否可以在 Scala 中编写这样的混合特征?它会是什么样子?

Arc*_*heg 4

更新这是宏。由于准引用,这比我想象的要少得多。他们很棒。这段代码只做了一点点,你可能需要改进它。它可能没有考虑到一些特殊情况。它还假设父类和它的方法都没有类型参数,它只包装给定类或特征的方法,但不包装它的父方法,如果你有辅助构造函数等,它可能不起作用。我仍然希望它能给你不幸的是,如何满足您的特定需求并使其适用于所有情况的想法现在对我来说是一项艰巨的任务。

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 } """}
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您不想创建实例,但确实只想为您的特征添加日志记录,以便可以进一步混合,则可以使用相对相同的代码来完成此操作,但使用宏天堂类型注释:http://docs。 scala-lang.org/overviews/macros/annotations这些允许您标记类定义并在定义内执行修改


你可以用 做你想做的事情Dynamic,但有一个问题 - 你不能将其设为原始类型,所以它不是 mixin。仅当类型检查失败时,动态才开始工作,因此您不能混合实际类型(或者我不知道该怎么做)。真正的答案可能需要宏(正如 @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
Run Code Online (Sandbox Code Playgroud)

您可以Dynamic在这里阅读更多信息:https://github.com/scala/scala/blob/2.12.x/ ​​src/library/scala/Dynamic.scala