Wrap function implementations returning a specific type into another function programatically

北战南征 提交于 2020-12-29 08:19:07

问题


I would like to wrap all the user defined functions in a scala project that return a certain type T, into a function that accepts a T and the function name as parameters.

eg.

given this function is in scope:

 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
}

the user can send metrics for their functions which return Try by doing

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

but I would like the user to only have to define

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

and have his business logic that returns Try wrapped into withMetrics implicitly.

Note that the user should not have to annotate code, as that could lead to him forgetting about it. Instead all the user functions defined in his project should be wrapped into withMetrics automatically.

How can I achieve this by using Scala 2 or dotty macros? Or can this be achieved in another way?


回答1:


You can create macro annotation and annotate with it all classes, objects and traits where you want to instrument your methods.

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

This doesn't modify nested methods and methods in inner classes, objects, traits. If necessary this can be done too with scala.reflect.api.Trees.Traverser/Transformer. Or you can just annotate inner classes, objects, traits when necessary.



来源:https://stackoverflow.com/questions/56917025/wrap-function-implementations-returning-a-specific-type-into-another-function-pr

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