如何使用生成器模式返回带有Scala宏的内部对象

如何使用生成器模式返回带有Scala宏的内部对象,scala,scala-macros,Scala,Scala Macros,这个问题有点复杂,所以我将用一些序言来补充它,以便理解问题背后的驱动程序,以及演示它的代码示例 具有特征的不可变构建器模式 宇宙模式 最后 问题来了! 不可变构建器模式 我非常喜欢特性,但它们会给不可变构建器模式带来问题。例如,当我创建一个继承builder特性的对象时,当我对其调用一个“set”方法时,builder特性应该返回一个与原始对象完全相同的克隆,而不是我刚才设置的值 为了解决这个问题,我经常使用类型语句 trait Builder { type RealBuilder <

这个问题有点复杂,所以我将用一些序言来补充它,以便理解问题背后的驱动程序,以及演示它的代码示例

具有特征的不可变构建器模式 宇宙模式 最后 问题来了! 不可变构建器模式

我非常喜欢特性,但它们会给不可变构建器模式带来问题。例如,当我创建一个继承builder特性的对象时,当我对其调用一个“set”方法时,builder特性应该返回一个与原始对象完全相同的克隆,而不是我刚才设置的值

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

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 {
   }
}
这里的好处是,在宇宙中定义的所有东西都共享相同的泛型,以及这些类的代码,它们的使用不会受到大量泛型的污染

最后是问题

我想把一个函数传递给宇宙中的构建者,得到一个漂亮的“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[OuterInnerBuilder]的对象,它真正想要做的是返回'c.prefix'的InnerBuilder类

很遗憾,我不完全理解[]符号是Scala。所以下面的内容可能只是幼稚的。我尝试返回c.Expr[c.prefix.actualType],这是我想要的“想法”,但显然不正确


有人能告诉我如何将类型安全性恢复到此宏吗?

我想您可能对Context.PrefixType和Expr.value感兴趣。下面是我们测试套件中的一个相关示例:

此外,实际上不会丢失任何类型安全性,因为2.10中的宏扩展具有这样一个有趣的特性,即允许使用最精确的类型,而不仅仅是其签名中指定的类型,例如查看。在2.11中,这将有所改变,但这种细化返回类型的功能仍将保留


最后,知道在2.11中不再需要使用Expr和具体化可能会让人松一口气。使用2.11附带的quasiquotes,2.10中也可以通过宏paradise编译器插件使用它,您几乎可以以任何方式将树组合在一起,而无需对齐类型。使用更新的编写宏impls的规则,宏impls可以获取并返回c.Tree,因此您无需考虑在c.Expr的[]中放置什么。

我想您可能对Context.PrefixType和Expr.value感兴趣。下面是我们测试套件中的一个相关示例:

此外,实际上不会丢失任何类型安全性,因为2.10中的宏扩展具有这样一个有趣的特性,即允许使用最精确的类型,而不仅仅是其签名中指定的类型,例如查看。在2.11中,这将有所改变,但这种细化返回类型的功能仍将保留


最后,知道在2.11中不再需要使用Expr和具体化可能会让人松一口气。使用2.11附带的quasiquotes,2.10中也可以通过宏paradise编译器插件使用它,您几乎可以以任何方式将树组合在一起,而无需对齐类型。使用更新的编写宏impls的规则,宏impls可以获取并返回c.Tree,因此您无需考虑在c.Expr的[]中放入什么。

看起来很棒。我要做一个“gash”程序并尝试一下。4个小时后,我决定使用scala编译器的里程碑版本来获得Quasikotes太痛苦了。SBT给了我一大堆麻烦,因为它的编译器接口没有编译,然后是barfing,所有的scala开源项目我都要下载,下载它们的所有依赖项。。。当我试图生成一个开源项目时,在里程碑编译器中加入依赖项可能是个坏主意。明天我将尝试你在Context.PrefixType等中给出的想法。再次感谢您的帮助宏天堂插件如何?它在您的sbt构建中只有一行,如果您只使用Quasiquetes,它不会使您的库依赖于paradise插件。细节:我没有意识到:我天真地认为,如果我依赖于编译器插件,那么依赖我的所有项目也会如此。我明天会调查的:谢谢你看起来很棒。我要做一个“割伤”计划
4个小时后,我决定使用scala编译器的里程碑版本来获得Quasikotes太痛苦了。SBT给了我一大堆麻烦,因为它的编译器接口没有编译,然后是barfing,所有的scala开源项目我都要下载,下载它们的所有依赖项。。。当我试图生成一个开源项目时,在里程碑编译器中加入依赖项可能是个坏主意。明天我将尝试你在Context.PrefixType等中给出的想法。再次感谢您的帮助宏天堂插件如何?它在您的sbt构建中只有一行,如果您只使用Quasiquetes,它不会使您的库依赖于paradise插件。细节:我没有意识到:我天真地认为,如果我依赖于编译器插件,那么依赖我的所有项目也会如此。我明天会调查的:谢谢