Mixin to wrap every method of a Scala trait

时光总嘲笑我的痴心妄想 提交于 2020-12-29 06:58:07

问题


Suppose I have a trait Foo with several methods. I want to create a new trait which extends Foo but "wraps" each method call, for example with some print statement (in reality this will be something more complicated / I have a couple of distinct use cases in mind).

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

I can do this manually, by overriding each method. But this seems unnecessarily verbose (and all too easy to call the wrong super method):

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

I was hoping to write a mixin trait, that I would be able to reuse with other traits, and use as:

trait FooWrapped extends Foo with PrintCall

That way I don't have to manually override each method (the mixin would do this for me).

Is it possible to write such a mixin trait in Scala? What would it look like?


回答1:


Update Here is the macro. It was much less painful than I thought it will be because of quasiquotes. They are awesome. This code does only a little and you probably will have to improve it. It may not account some special situations. Also it assumes that neither parent class nor it's method has type params, it wraps only the methods of the given class or trait, but not it's parents methods, it may not work if you have auxilary constructors etc. Still I hope it will give you an idea of how to do that for your specific needs, making it working for all of the situations unfortunately is too big job for me right now.

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

If you do not want to create an instance, but you do want to add logging only for your trait so you could mixin further, you can do this with relatively the same code, but using macro paradise type annotations: http://docs.scala-lang.org/overviews/macros/annotations These allow you to tag your class definitions and perform modifications right inside the definitions


You could do something like you want with Dynamic, but there is a catch - you can't make it of original type, so it's not a mixin. Dynamic starts to work only if type checks fails, so you can't mixin real type (or I do not know how to do that). The real answer would probably require macros (as @AlexeyRomanov suggested in comments), but I am not sure how to write one, maybe I'll come up with it later. Still Dynamic might work for you if you are not looking for DI here

  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

You can read more about Dynamic here: https://github.com/scala/scala/blob/2.12.x/src/library/scala/Dynamic.scala



来源:https://stackoverflow.com/questions/36244257/mixin-to-wrap-every-method-of-a-scala-trait

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!