此文档页面特定于 Scala 2 中提供的功能,这些功能在 Scala 3 中已删除或被其他功能取代。除非另有说明,此页面中的所有代码示例均假定你使用的是 Scala 2。
实验性
Eugene Burmako
将宏分离为黑盒宏和白盒宏是 Scala 2.11.x 和 Scala 2.12.x 的一项功能。Scala 2.10.x 中不支持黑盒/白盒分离。Scala 2.10.x 的宏天堂中也不支持它。
宏是如何工作的?
随着宏成为 Scala 2.10 正式版本的一部分,研究和工业界的程序员们已经找到了使用宏来解决各种问题的新方法,远远超出了我们最初的预期。
事实上,宏迅速成为我们生态系统的重要组成部分,以至于在 Scala 2.10 发布仅仅几个月后,当宏以实验性功能引入时,我们召开了 Scala 语言团队会议,并决定对宏进行标准化,并使其成为 Scala 2.12 的一个成熟功能。
更新事实证明,在 Scala 2.12 中稳定宏并不那么简单。我们对此进行的研究导致建立了 Scala 的一个新的元编程基础,称为 scala.meta,其第一个测试版预计将与 Scala 2.12 同时发布,并可能在以后的 Scala 版本中包含。与此同时,Scala 2.12 将不会对反射和宏进行任何更改 - 所有内容都将保持与 Scala 2.10 和 Scala 2.11 中的实验性相同,并且不会删除任何功能。但是,即使编写本文档的情况发生了变化,信息仍然相关,因此请继续阅读。
宏风格繁多,因此我们决定仔细研究它们,以找出哪些应该放入标准中。这需要回答几个重要的问题。为什么宏工作得如此出色?为什么人们使用它们?
我们的假设是,这是因为在 def 宏中表达的难以理解的元编程概念依赖于熟悉的类型化方法调用的概念。因此,用户编写的代码可以吸收更多含义,而不会变得臃肿或失去可理解性。
黑盒和白盒宏
但是,有时 def 宏超越了“仅仅是一个常规方法”的概念。例如,宏扩展可能会产生比宏返回类型更具体的类型的表达式。在 Scala 2.10 中,这种扩展将保留其精确类型,如 Stack Overflow 上的 “Scala 宏的静态返回类型” 文章中所强调的。
这个奇怪的功能提供了额外的灵活性,启用了 假类型提供程序、扩展的香草具体化、fundep 具体化 和 提取器宏,但它也牺牲了清晰度 - 对于人类和机器都是如此。
为了具体化在表现得像普通方法一样的宏和细化其返回类型的宏之间的关键区别,我们引入了黑盒宏和白盒宏的概念。忠实遵循其类型签名的宏称为黑盒宏,因为它们的实现与理解其行为无关(可以视为黑盒)。在 Scala 的类型系统中不能具有精确签名的宏称为白盒宏(白盒 def 宏确实有签名,但这些签名只是近似值)。
我们认识到黑盒和白盒宏的重要性,但我们对黑盒宏更有信心,因为它们更容易解释、指定和支持。因此,我们标准化宏的计划只包括黑盒宏。稍后,我们也可能会将白盒宏纳入我们的计划,但现在还为时过早。
编纂区别
在 2.11 版本中,我们通过在 def 宏的签名中表达黑盒和白盒宏之间的区别,迈出了标准化的第一步,以便 scalac
可以对这些宏进行不同的处理。这只是一个准备步骤,因此在 Scala 2.11 中,黑盒和白盒宏仍然是实验性的。
我们通过用 scala.reflect.macros.blackbox.Context
和 scala.reflect.macros.whitebox.Context
替换 scala.reflect.macros.Context
来表达区别。如果宏实现被定义为第一个参数为 blackbox.Context
,那么使用它的宏定义将被视为黑盒,类似地,对于 whitebox.Context
也是如此。当然,出于兼容性原因,原始 Context
仍然存在,但它会发出弃用警告,鼓励在黑盒和白盒宏之间进行选择。
黑盒 def 宏的处理方式不同于 Scala 2.10 的 def 宏。Scala 类型检查器对它们应用了以下限制
- 当黑盒宏的应用扩展到树
x
时,扩展将被包装到类型归因(x: T)
中,其中T
是黑盒宏的声明返回类型,其中类型参数和路径依赖项与正在扩展的特定宏应用一致。这使黑盒宏作为 类型提供程序 的实现工具无效。 - 在 Scala 的类型推断算法完成工作后,如果黑盒宏的应用仍然具有未确定的类型参数,则这些类型参数将被强制推断,其方式与对普通方法进行类型推断的方式完全相同。这使得黑盒宏无法影响类型推断,从而禁止 fundep 物化。
- 当黑盒宏的应用被用作隐式候选时,在宏被选为隐式搜索结果之前,不会执行任何扩展。这使得 动态计算隐式宏的可用性 变得不可能。
- 当黑盒宏的应用在模式匹配中用作提取器时,它会触发一个无条件的编译器错误,从而防止使用宏实现的模式匹配自定义。
白盒 def 宏的工作方式与 Scala 2.10 中使用的 def 宏的工作方式完全相同。没有任何限制,因此在 2.10 中可以使用宏完成的所有操作都可以在 2.11 和 2.12 中完成。