如何使用构建器模式使用Scala宏返回内部对象

时间:2014-01-09 11:51:48

标签: scala scala-macros

这个问题有点复杂,所以我正在使用一些序言来构建它,以便可以理解问题背后的驱动程序以及演示它的代码示例。

  • 具有特征的不可变生成器模式
  • 宇宙模式
  • 最后 这个问题!

不可变构建器模式

我非常喜欢traits,但是他们可以解决不可变构建器模式的问题。例如,当我创建一个继承构建器特征的对象时,当我在其上调用'set'方法时,构建器特征应该返回原始的相同克隆,除了我刚设置的值。

为了解决这个问题,我经常使用类型语句

trait Builder {
  type RealBuilder <: Builder
   ...

这不是很好用,但它适用于大多数用途。

宇宙模式

我正在尝试从Scala编译器中获取的想法。我有大约十个泛型,但我不希望用户看到它们中的大多数,并且泛型通常以复杂的方式链接在一起

trait Universe[X] {
   type A
   type B
   type RealBuilder <: MyBuilder
   type RealBuiltThing <: MyBuiltThing
   trait MyBuilder {
      def build: 
   }

   trait MyBuiltThing {
   }
}

所以这里的好处是宇宙中定义的所有内容都共享相同的泛型,这些类的代码及其用法不会受到大量泛型的污染。

最后一个问题

我想将一个函数传递给Universe中的构建器并获得一个很好的'toString。所以我正在包装函数(在这个简单的例子中只是X),然后将包装的对象传递给setIt方法

class Wrapper[X](x: X, string: String) {
  override def toString() = string
}

object Outer {

  def someProperty[X: c.WeakTypeTag](c: Context)(someValue: c.Expr[X]): 
          c.Expr[Outer#InnerBuilder] = { // <----------- This is the first line referenced below
    import c.universe._
    val xString = show(someValue.tree)
    reify { c.Expr[Outer#InnerBuilder](c.prefix.tree).splice.setIt(new Wrapper[X](someValue.splice, c.literal(xString).splice)) } 
  }
}

class Outer {

  trait InnerBuilder {
    type RealInnerBuilder <: InnerBuilder;
    type B;
    def someProperty[X](someValue: X) = macro Outer.someProperty[X]
    def setIt[X](w: Wrapper[X]): RealInnerBuilder = {
      println("Setting it to " + w) //shows that the code got here
      this.asInstanceOf[RealInnerBuilder] //in practice would return a new instance that held the wrapper
    }
  }

  class InnerBuilder1 extends InnerBuilder {
    type RealInnerBuilder = InnerBuilder1
  }

}

如果我现在创建一个InnerBuilder1并调用'someProperty',则执行println语句。乌拉!

但它很大但是......我失去了一些类型的安全性。伴随对象返回c.Expr [Outer#InnerBuilder]的对象,它真正想做的是返回'c.prefix'的InnerBuilder类。

我很伤心地说,我不完全了解的[]符号是斯卡拉。所以以下可能只是天真。我已经尝试返回c.Expr [c.prefix.actualType],这是我想要的“想法”,但显然不正确。

有人可以告诉我如何将类型安全带回到这个宏吗?

1 个答案:

答案 0 :(得分:4)

我想您可能对Context.PrefixTypeExpr.value感兴趣。以下是我们的测试套件中的相关示例:https://github.com/scala/scala/blob/d5801b9eee7df49894c05dea430a56190cae2112/test/files/run/macro-def-path-dependent-b/Impls_Macros_1.scala#L19

此外,实际上没有类型安全性丢失,因为2.10中的宏扩展具有允许最精确类型的有趣属性,而不仅仅是在其签名中指定的内容(例如,查看http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/)。在2.11中,这将改变一点(http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html),但这种改进返回类型的能力仍将保留。

最后,要知道在2.11中不再需要使用Exprreify,可能会感到宽慰。随着2.11附带的quasiquotes(也可以通过宏天堂编译器插件在2.10中提供),您可以将树几乎以您想要的任何方式放在一起,而无需使类型对齐。使用编写宏impl的更新规则,宏impl可以获取并返回c.Tree,因此您不需要考虑在[]的{​​{1}}中放置什么内容