Scala宏自动生成流畅的构建器

pat*_*rit 1 scala builder scala-macros scala-macro-paradise scalameta

我正在与外部Java API交互,如下所示:

val obj: SomeBigJavaObj = {
  val _obj = new SomeBigJavaObj(p1, p2)
  _obj.setFoo(p3)
  _obj.setBar(p4)
  val somethingElse = {
    val _obj2 = new SomethingElse(p5)
    _obj2.setBar(p6)
    _obj2
   }
  _obj.setSomethingElse(somethingElse)
  _obj
}
Run Code Online (Sandbox Code Playgroud)

基本上,Java API公开了.setXXXX许多返回void和设置内容的方法.我无法控制这些外部POJO.

因此,我想编写一个流畅的buildScala宏来检查对象,并.withXXXX()为每个void setXXXX()返回的方法创建一个builder-pattern类型方法 this:

val obj: SomeBigJavaObj =
  build(new SomeBigJavaObj(p1, p2))
    .withFoo(p3)
    .withBar(p4)
    .withSomethingElse(
       build(new SomethingElse(p5))
         .withBar(p6)
         .result()
    )
    .result()
Run Code Online (Sandbox Code Playgroud)

这可能吗?我知道我无法使用def宏生成新的顶级对象,所以我可以使用类似的人体工程学设置.

余杰水*_*余杰水 5

使用宏并不复杂; 对IDE不友好(例如:代码完成; ......);

//编辑1:支持多个参数

实体:

public class Hello {
  public int    a;
  public String b;


  public void setA(int a) {
    this.a = a;
  }

  public void setB(String b) {
    this.b = b;
  }

  public void setAB(int a , String b){
    this.a = a;
    this.b = b;
  }
}
Run Code Online (Sandbox Code Playgroud)

宏代码:import scala.language.experimental.macros import scala.reflect.macros.whitebox

trait BuildWrap[T] {
  def result(): T
}

object BuildWrap {
  def build[T](t: T): Any = macro BuildWrapImpl.impl[T]
}

class BuildWrapImpl(val c: whitebox.Context) {

  import c.universe._

  def impl[T: c.WeakTypeTag](t: c.Expr[T]) = {
    val tpe = c.weakTypeOf[T]
    //get all set member
    val setMembers = tpe.members
      .filter(_.isMethod)
      .filter(_.name.toString.startsWith("set"))
      .map(_.asMethod)
      .toList
    // temp value ;
    val valueName = TermName("valueName")

    val buildMethods = setMembers.map { member =>
      if (member.paramLists.length > 1)
        c.abort(c.enclosingPosition,"do not support Currying")

      val params = member.paramLists.head
      val paramsDef = params.map(e=>q"${e.name.toTermName} : ${e.typeSignature}")
      val paramsName = params.map(_.name)

      val fieldName = member.name.toString.drop(3)//drop set
      val buildFuncName = TermName(s"with$fieldName")
      q"def $buildFuncName(..$paramsDef ) = {$valueName.${member.name}(..$paramsName);this} "
    }


    val result =
      q"""new BuildWrap[$tpe] {
        private val $valueName = $t
        ..${buildMethods}
        def result() = $valueName

       }"""

    // debug
    println(showCode(result))
    result
  }
}
Run Code Online (Sandbox Code Playgroud)

测试代码:

val hello1: Hello = BuildWrap.build(new Hello).withA(1).withB("b").result()
assert(hello1.a == 1)
assert(hello1.b == "b")

val hello2: Hello = BuildWrap.build(new Hello).withAB(1, "b").result()
assert(hello2.a == 1)
assert(hello2.b == "b")
Run Code Online (Sandbox Code Playgroud)