Scala3-Macro系统Tasty进展

Scala3-Macro系统Tasty进展

Scala3 重新设计Macro系统,这是官网英文原文
翻译的很烂,全当学习笔记而已,仅供参考

Or: Scala in a (Tasty) Nutshell

如何迁移到 Scala 3这篇博文中提到最大的一个问题是关于宏的问题。
目前我们正在努力将Tasty和macros对齐,接下来谈一谈我们的想法.

What is Tasty?

Tasty是Scala3的高级交换格式。它基于类型化的抽象语法树
这些树在某种意义上包含了Scala程序中的所有信息。
它们表示程序的语法结构,还包含有关类型和位置的完整信息.
Tasty在语法检查之后(这样所有的类型都显式的知道了,并且隐式的东西都已经解释过了)给代码做一个快照,
但是在快照之前不会经过任何转换,因此所有的信息都不会丢失。
放语法树的文件为了紧凑会压缩优化(有点像javascript的压缩),这意味着我们可以在任何编译器运行期间
生成完整的Tasty语法树,即便是单独编译也不依赖任何其他东西。

Tasty语法树可以有很多用途

  • 编译器可以用来支持单独编译
  • 基于LSP-based language server 的语言可以支持超链接,自动补全,文档,查找引用,以及全局的重命名重构操作
  • 构建工具可以在不同的平台上交叉构建,并将代码以二进制的方式迁移合并
  • 优化器和分析器可以使用它进行深入的代码分析和高级代码生成

以上的CASE前两个已经完成,可以使用,另外两个在将来可以考虑实现

所以,到底什么是Tasty, 最新的Scala3的 dotc编译器的Tasty版本描述可以参见这里
TastyFormat.scala

What Does Tasty Have to Do with Macros?

事实证明,Tasty也为新一代基于反射的宏提供了极好的基础,有可能解决当前版本中的许多问题。

当前Def Macros的第一个问题是它们完全依赖于当前的Scala编译器(内部名为nsc)
事实上,def macros像是nsc内部结构之上的一层薄木板罩子, 这使得它功能非常强大,但也非常脆弱和难以使用
正因为如此,他们一生都处于“实验”状态
由于Scala 3使用不同的编译器(dotc),旧的基于反射的宏系统将不会移植,所以做一个不同并且更好的东西。

对于当前的宏的另一种批评是它缺乏一个基础。 Scala3已经有一个元编程的基础设施, 特别是一个非常好的explored基础。
Quotes and Splices 是一种支持staging的方法,
只需要向语言添加两个运算:Quote (')表示引用代码表达式,而splice(~)用来将一段代码插入另一段代码中。
这个灵感来自于termporal logic
MetaOCaml
我们通过将两个时态操作符',~以及scala3 的inline功能得到了一个非常高层次的macro系统

  • inline 将代码从定义站点复制到调用站点
  • (') 将代码转换为语法树
  • (~) 在其他代码中嵌入语法树

这种处理宏的方法非常优雅,并且具有惊人的表达能力。
但这可能有点太偏重原理,仍然有许多零碎的边角的问题需要处理, 特别是:

  • 语法树是晦涩难懂的,我们缺少分解它们并分析其结构和内容的方法。
  • 我们只能quote和splice表达式,但不能引用其他程序结构,如定义或参数。

我们一直在寻找一种通过分解和重构语法树的方式来加强元编程。
这里的主要问题是太难选择了,因为基本上有无数种方法可以暴露底层结构。
quasiquotes还是语法树?哪些应该明确的暴露出来?
辅助类型和操作是什么?

如果我们在这里要做一个选择的话,我们怎么知道这个选择对于今天的用户是正确的?
如何在未来保证Api的稳定性?选择越多导致局面越尴尬,这是困扰def macros的根本原因。
为了解决这一难题,我们计划自底向上来而不是自顶向下的方式。
我们确立一下原则:

The reflective layer of macros will be isomorphic to Tasty.
Tasty会将反射和宏同构支持

将会有一下几点好处:

  • 完整性. Tasty是Scala3的交换格式,因此基于Tasty的反射API将没有信息的丢失
  • 稳定. Tasty作为交换格式会保持稳定,会按照严格的版本系统来谨慎管理Tasty的演进升级。因此反射的API可以以一种可控的方式发展。
  • 独立于编译器. Tasty的设计将独立于支持它的编译器。除了Dotty的实现之外,在概念上也可以证明’scalac’也能生成Tasty语法树,甚至JAVA也可以生成。这就意外着反射API可以容易的移植到新的编译器,如果一个编译器支持Tasty格式的交换件,则可以同时支持反射API.

Scala in a Nutshell

作为实现这一目标的第一步,我们正在研究一套独立于编译器的数据结构来表示Tasty。
目前的状态是已经提供了一个高层次的数据结构,用在Tasty的各个方面。
目前大概有200行的定义,它能翻译类型检查后scala程序中包含的每一条信息。
200行比mini-lisp的解释器要大,但是比起一个成熟的编译器前段差不多30000行要小的多。

Next Steps

下一步WIP, 是将这些定义关联到Tasty文件格式
我们将它重写为提取器来实现这一点,提取器根据dotc编译器使用的数据结构实现每种数据类型,
然后以Tasty格式对这些数据进行pickle和unpickle
另外一个有趣的选项是,直接书写Tasty的picklers和unpickler, 他们直接工作在反射语法树上。

Once this is done, we need to define and implement semantic operations such as

一旦这些工作完成以后,我们开始定义和实现语义和语法实现

  • what are the members that can be selected on this expression?
  • which subclasses are defined for a sealed trait?
  • does this expression conform to some expected type?

  • 可以在此表达式上选择哪些成员?

  • 哪些子类是为一个密封特性定义的?
  • 此表达式是否符合某些预期类型?

最后,我们将反射层和现有的基于quotes和spilices的宏链接起来, 这个看起来并不难。
实际上,我们需要在scala.quoted.Expr[T]类型的高级树和Tasty.Term类型的低级树之间定义一对映射。
将一个高级树映射到一个低级树简单的意思就是要暴露它的结构。
将低层语法树树映射到高层scala.quoted.Expr[T]就意味着检查低层语法树是否确实具有给定的T类型。
差不多就是这些。

Future Macros

如果以上方案被接受,很大程度上决定了Scala3上Macro的样子,更重要的是,这些都在类型检查完成以后运行,这也正是Tasty语法树
生成使用的时候。在类型检查以后运行Macro有很多优点

  • it is safer and more robust, since everything is fully typed,
  • it does not affect IDEs, which only run the compiler until typechecking is done,
  • it offers more potential for incremental compilation and parallelization.

  • 它更安全、更健壮,没有类型擦除,类型完整保留

  • 它不影响IDE,IDE只在类型检查完成之前运行编译器
  • 它为增量编译和并行化提供了更大的潜力

不过这样做也限制了Macro的标识类型,Macro将是blackbox.
这意味着从类型检查中看到的扩展表达式不能受Macro扩展的影响。只要满足这个约束条件,就能同时支持经典的def宏和宏注释。

例如,可以定义一个宏注释@json,将JSON的序列添加到类型种。与当前macro paradise
annotation macros(并不属于Scala的正式发行版本)
的不同之处是, 在Scala3中由于注释驱动的扩展是在类型检查之后发生的,因此生成的序列化程序只能在下游项目中看到

(译注: 这段没看明白,生翻过来,见谅)

我们相信通过使用更具表现力的计算类型可以在一定程度上环境Whitebox macros的不足,
[Dotty PR 3844](https://github.com/lampepfl/dotty/pull/3844中发布了该系统的草图。

Scala3语言还将直接包含一些更高级的结果,目前为止需要高级的Macro方式来定义。
尤其:

  • 直接通过by-name parameters为Lazy implicit建模,取代了之前通过Macro的方式
  • 原生的 type lambdas 减少了kind projector的需求.
  • 将会用一种不用宏的方式来派生类,其中有Kittens, Magnolia,scalaz-deriving,目前还在评估具体是哪一种替代方案。主要目标是开发一个易于使用且在编译和运行期间都能稳定的方案,第二个目标是通用性,只要他与主目标不冲突

Meta Programming in the Large

未来Scala3宏的设计旨在取代现有的Macro和scala.reflect. 目前还有一个元编程系统Scalameta相当有竞争力, 他提供了高质量的语法和予以分析以及独立于Scala编译器的代码生成工具,顾名思义,ScalaMeta是在元级别上运行的,也就是说,他一程序为输入,生成语法和语义信息,或者以重写的程序为输入出。相反,Macro系统集成在语言中,并在翻译过程中扩展程序,这两个项目之间可能存在潜在的协作性。可以举两种可能性。

  • Scalameta或者由它派生出来的SemanticDB项目可以直接从Tasty获取信息,这将使得他们完全独立于编译器之外。
  • IDE可以在单个项目上使用Tasy, 而对于复杂的多项目多语言的构造请参考SemanticDB

Please Give Us Your Feedback!

如果你对于Macro的roadmap有更好的想法,欢迎到这里讨论,thread on Scala Contributors.
你们的建议都非常重要,加入Dotty贡献你的力量,一起重建Scala未来

评论