如何使用构建器模式通过 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.PrefixType
和Expr.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 宏返回内部对象的主要内容,如果未能解决你的问题,请参考以下文章