首页 文章

Mixin包装Scala特征的每个方法

提问于
浏览
4

假设我有几种方法的特征 Foo . 我想创建一个新的特性,扩展 Foo 但每个方法调用"wraps",例如使用一些print语句(实际上这将是更复杂的东西/我有几个不同的用例) .

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特性?它会是什么样子?

1 回答

  • 3

    Update 这是宏 . 由于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 做你想做的事情,但是有一个问题 - 你可以't make it of original type, so it'不是混合 . 动态仅在类型检查失败时才开始工作,因此您可以稍后再提出它 . 如果你不在这里寻找DI,那么 Dynamic 可能对你有用

    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

相关问题