【问题标题】:How to reduce boilerplate code with Scala Macros in Scala 2?如何在 Scala 2 中使用 Scala 宏减少样板代码?
【发布时间】:2021-11-03 10:17:53
【问题描述】:

对于这样的代码,有很多样板代码。

object TupleFlatten {
  import shapeless._
  import ops.tuple.FlatMapper
  import syntax.std.tuple._

  trait LowPriorityFlat extends Poly1 {
    implicit def default[T] = at[T](Tuple1(_))
  }

  object Flat extends LowPriorityFlat {
    implicit def caseTuple1[P <: Tuple1[_]](implicit fm: FlatMapper[P, Flat.type]): Flat.Case[P] {
      type Result = FlatMapper[P, Flat.type]#Out
    } =
      at[P](_.flatMap(Flat))

    implicit def caseTuple2[P <: Tuple2[_, _]](implicit fm: FlatMapper[P, Flat.type]) =
      at[P](_.flatMap(Flat))

    implicit def caseTuple3[P <: Tuple3[_, _, _]](implicit fm: FlatMapper[P, Flat.type]) =
      at[P](_.flatMap(Flat))

    implicit def caseTuple4[P <: Tuple4[_, _, _, _]](implicit fm: FlatMapper[P, Flat.type]) =
      at[P](_.flatMap(Flat))
  }
}

有什么方法可以自动生成如下代码,从Tuple1Tuple22

implicit def caseTupleN[P <: TupleN[???]](implicit fm: FlatMapper[P, Flat.type]) =
      at[P](_.flatMap(Flat))

怎么做?

【问题讨论】:

  • 使用源生成器怎么样?

标签: scala shapeless scala-macros


【解决方案1】:

当然,您可以使用 macro annotation 来生成隐式

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

object Macros {

  @compileTimeOnly("enable macro annotations")
  class genImplicits(n: Int) extends StaticAnnotation {
    def macroTransform(annottees: Any*): Any = macro genImplicitsMacro.impl
  }

  object genImplicitsMacro {
    def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
      import c.universe._
      val n = c.prefix.tree match {
        case q"new genImplicits(${n1: Int})" => n1
      }
      val implicits = (1 to n).map { k =>
        val undescores = Seq.fill(k)(tq"${TypeName("_")}")
        q"""
          implicit def ${TermName("caseTuple" + k)}[P <: _root_.scala.${TypeName("Tuple" + k)}[..$undescores]: _root_.shapeless.IsTuple](implicit
            fm: _root_.shapeless.ops.tuple.FlatMapper[P, this.type]
          ): this.Case.Aux[P, fm.Out] = this.at[P].apply[fm.Out](_.flatMap(this))
        """
      }
      annottees match {
        case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
          q"""
            $mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
              import _root_.shapeless.syntax.std.tuple._
              ..$implicits
              ..$body
            }
          """
      }
    }
  }
}

import Macros.genImplicits
import shapeless.Poly1

trait LowPriorityFlat extends Poly1 {
  implicit def default[T]: Case.Aux[T, Tuple1[T]] = at[T](Tuple1(_))
}

@genImplicits(4)
object Flat extends LowPriorityFlat

Flat(((1, (2, 3), (4, (5, 6, 7))), (8, 9))) // (1,2,3,4,5,6,7,8,9)

Flat(((1, (2, 3), (4, (5, 6, 7))), (8, 9)))@genImplicits(22) 的编译需要很长时间,尽管 @genImplicits(22) 本身的扩展非常快。

您也可以使用Shapeless BoilerplateScala genprodsbt-boilerplateScalameta 生成代码。

但我看不出这比仅使用带有上下文绑定 IsTuple 而不是上限的 Shapeless 的更简单定义更好

import shapeless.ops.tuple.FlatMapper
import shapeless.{IsTuple, Poly1}
import shapeless.syntax.std.tuple._

trait LowPriorityFlat extends Poly1 {
  implicit def default[T]: Case.Aux[T, Tuple1[T]] = at[T](Tuple1(_))
}

object Flat extends LowPriorityFlat {
  implicit def caseTuple[P: IsTuple](implicit fm: FlatMapper[P, Flat.type]): Case.Aux[P, fm.Out]  =
    at[P](_.flatMap(Flat))
}

Flat(((1, (2, 3), (4, (5, 6, 7))), (8, 9))) // (1,2,3,4,5,6,7,8,9)

甚至没有界限

import shapeless.ops.tuple.FlatMapper
import shapeless.{IsTuple, Poly1}

trait LowPriorityFlat extends Poly1 {
  implicit def default[T]: Case.Aux[T, Tuple1[T]] = at[T](Tuple1(_))
}

object Flat extends LowPriorityFlat {
  implicit def caseTuple[P](implicit fm: FlatMapper[P, Flat.type]): Case.Aux[P, fm.Out]  =
    at[P](fm(_))
}

Flat(((1, (2, 3), (4, (5, 6, 7))), (8, 9))) // (1,2,3,4,5,6,7,8,9)

请注意,使用带有类型投影Flat.Case[P] { type Result = FlatMapper[P, Flat.type]#Out } aka Flat.Case.Aux[P, FlatMapper[P, Flat.type]#Out] 的隐式返回类型有时会导致隐式解析出现问题(除非您知道自己在做什么)。最好在隐式Flat.Case.Aux[P, fm.Out]的返回类型中使用路径相关类型。

【讨论】:

    猜你喜欢
    • 2017-05-18
    • 1970-01-01
    • 1970-01-01
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多