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

Posted

技术标签:

【中文标题】如何使用构建器模式通过 Scala 宏返回内部对象【英文标题】:How do I use builder pattern to return inner object with Scala macros 【发布时间】:2014-01-09 11:51:48 【问题描述】:

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

具有特征的不可变生成器模式 宇宙格局 终于 问题!

不可变生成器模式

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

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

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 类。

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

谁能告诉我如何将类型安全带回这个宏?

【问题讨论】:

【参考方案1】:

我想你可能对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 中不再需要使用 Expr's 和 reify,这可能会让人感到宽慰。使用 2.11 附带的 quasiquotes(在 2.10 中也可以通过宏天堂编译器插件获得),您几乎可以以任何您想要的方式将树组合在一起,而无需使类型对齐。随着宏 impls 编写规则的更新,宏 impls 可以获取和返回c.Tree's,这样您就无需考虑在c.Expr[]'s 中放入什么。

【讨论】:

看起来不错。我要让我成为一个“gash”程序并尝试一下。 经过 4 个小时的艰苦移植后,我决定使用里程碑版本的 scala 编译器来获得准引号太痛苦了。 SBT 给我带来了很多麻烦(编译器接口没有编译然后 barfing),我必须下载的所有 scala 开源项目,并下载它们的所有依赖项......当我试图产生一个开放的源项目,依赖里程碑编译器可能是一个坏主意。明天我将尝试你在 Context.PrefixType 等上给出的想法。再次感谢您的帮助 宏天堂插件怎么样?它是您的 sbt 构建中的 1 行,如果您只使用 quasiquotes 它不会使您的库依赖于天堂插件。详情:docs.scala-lang.org/overviews/macros/quasiquotes.html 我没有意识到:我天真地认为如果我依赖编译器插件,那么所有依赖我的项目也会如此。明天我会调查一下:谢谢

以上是关于如何使用构建器模式通过 Scala 宏返回内部对象的主要内容,如果未能解决你的问题,请参考以下文章

使用Scala宏无法解释的类型不匹配

使用 Arrow 创建具有错误处理的对象构建器 - 模式匹配多个 Either

设计模式之迭代器模式

设计模式之迭代器模式

使用条目和迭代器来构建数组。

数据库查询构建器有时会返回数组而不是作为排队作业运行的对象