如何在 Scala 2 中使用 Scala 宏减少样板代码?
How to reduce boilerplate code with Scala Macros in Scala 2?
对于这样的代码,有很多样板代码。
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))
}
}
有没有办法自动生成如下代码,从Tuple1
到Tuple22
implicit def caseTupleN[P <: TupleN[???]](implicit fm: FlatMapper[P, Flat.type]) =
at[P](_.flatMap(Flat))
怎么做?
当然,您可以生成隐式,例如 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 Boilerplate, Scala genprod, sbt-boilerplate or Scalameta 的代码生成。
但我看不出这比仅使用 Shapeless 和上下文绑定 IsTuple
而不是上限
的更简单的定义更好
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)
请注意,使用 return 类型的隐式和类型投影 Flat.Case[P] { type Result = FlatMapper[P, Flat.type]#Out }
又名 Flat.Case.Aux[P, FlatMapper[P, Flat.type]#Out]
有时会导致隐式解析出现问题(除非您知道自己在做什么)。最好在 return 类型的隐式 Flat.Case.Aux[P, fm.Out]
.
中使用依赖于路径的类型
对于这样的代码,有很多样板代码。
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))
}
}
有没有办法自动生成如下代码,从Tuple1
到Tuple22
implicit def caseTupleN[P <: TupleN[???]](implicit fm: FlatMapper[P, Flat.type]) =
at[P](_.flatMap(Flat))
怎么做?
当然,您可以生成隐式,例如 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 Boilerplate, Scala genprod, sbt-boilerplate or Scalameta 的代码生成。
但我看不出这比仅使用 Shapeless 和上下文绑定 IsTuple
而不是上限
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)
请注意,使用 return 类型的隐式和类型投影 Flat.Case[P] { type Result = FlatMapper[P, Flat.type]#Out }
又名 Flat.Case.Aux[P, FlatMapper[P, Flat.type]#Out]
有时会导致隐式解析出现问题(除非您知道自己在做什么)。最好在 return 类型的隐式 Flat.Case.Aux[P, fm.Out]
.