包装函数实现以编程方式将特定类型返回到另一个函数

时间:2019-07-06 19:23:40

标签: scala macros metaprogramming scala-macros dotty

我想将所有返回自定义类型T的scala项目中的所有用户定义函数包装到接受T和函数名称作为参数的函数中。

例如

鉴于此功能在范围之内:

 def withMetrics[T](functionName: String)(f: => Try[T]): Try[T] = {
    f match {
        case _: Success[T] => println(s"send metric: success for $functionName")
        case _: Failure[T] => println(s"send metric: failure for $functionName")
    }

    f
}

用户可以发送其功能的指标,并通过这样做返回Try

def userDefinedFunction: Try[_] =
    withMetrics("userDefinedFunction"){
        somethingRisky: Try[_]
    }

但是我希望用户只需要定义

def userDefinedFunction: Try[_] =
    somethingRisky: Try[_]

并且将返回Try的业务逻辑隐式包装到withMetrics中。

请注意,用户不必注释代码,因为这可能会使他忘记代码。 相反,应将其项目中定义的所有用户功能自动包装到withMetrics中。

如何通过使用Scala 2或dotty宏来实现此目标? 还是可以通过其他方式实现?

1 个答案:

答案 0 :(得分:2)

您可以创建宏注释,并在其中注释所有要检测方法的类,对象和特征。

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

object Macros {
  @compileTimeOnly("enable macro paradise (or -Ymacro-annotations in 2.13) to expand macro annotations")
  class withMetrics extends StaticAnnotation {
    def macroTransform(annottees: Any*): Any = macro WithMetricsMacro.impl
  }

  object WithMetricsMacro {
    def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
      import c.universe._

      def modify(stats: Seq[Tree]): Seq[Tree] = stats.map {
        case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" =>
          q"$mods def $tname[..$tparams](...$paramss): $tpt = withMetrics(${tname.toString}){ $expr }"
      }

      annottees match {
        case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
          q"""
             $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${modify(stats)} }
             ..$tail
            """
        case q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
          q"""
              $mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..${modify(stats)} }
              ..$tail
             """
        case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: Nil =>
          q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..${modify(stats)} }"

        case _ =>
          c.abort(c.enclosingPosition, "Not a class, object or trait ")
      }
    }
  }
}

import Macros._
import scala.util.{Failure, Success, Try}

object App {

  @withMetrics
  class A {
    def userDefinedFunction: Try[String] = Try("aaa")
  }

  def withMetrics[T](functionName: String)(f: => Try[T]): Try[T] = {
    f match {
      case _: Success[T] => println(s"send metric: success for $functionName")
      case _: Failure[T] => println(s"send metric: failure for $functionName")
    }

    f
  }

  def main(args: Array[String]): Unit = {
    (new A).userDefinedFunction // send metric: success for userDefinedFunction
  }
}

这不会修改嵌套方法以及内部类,对象,特征中的方法。如有必要,也可以使用scala.reflect.api.Trees.Traverser/Transformer完成。或者,您可以仅在必要时注释内部类,对象,特征。