Реализации функций обтекания, возвращающие определенный тип в другую функцию программно - PullRequest
2 голосов
/ 06 июля 2019

Я хотел бы обернуть все пользовательские функции в проекте scala, которые возвращают определенный тип T, в функцию, которая принимает 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 Ответ

2 голосов
/ 06 июля 2019

Вы можете создавать макро-аннотации и аннотировать с ними все классы, объекты и признаки, где вы хотите использовать свои методы.

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. Или вы можете просто комментировать внутренние классы, объекты, черты, когда это необходимо.

...