首页 > 解决方案 > 以编程方式将返回特定类型的函数实现包装到另一个函数中

问题描述

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

例如。

鉴于此功能在范围内:

 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 宏来实现这一点?或者这可以通过其他方式实现吗?

标签: scalamacrosmetaprogrammingscala-macrosdotty

解决方案


您可以创建宏注解并用它注解您想要检测方法的所有类、对象和特征。

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. 或者,您可以在必要时对内部类、对象、特征进行注释。


推荐阅读