问题描述
我正在开始使用 Scala 宏,它们很棒,但我遇到了类型化(又名类型检查)和非类型化 Tree
s 之间的区别.
I'm getting started with scala macros, they're awesome, but I'm running into the difference between typed (aka typechecked) and untyped Tree
s.
例如,由于某种原因,您不能使用经过类型检查的 Tree 调用 c.eval
.我在 scala 宏文档中找不到关于此类型检查"的文档(我知道他们仍在努力,这可能是某天需要添加的内容).
For example, you can't call c.eval
with a typechecked Tree for some reason. I can't find documentation on this 'typechecked' in the scala macros documentation (I know they're still working on that, this might be something that needs to be added some day).
对Tree
进行类型检查意味着什么?为什么它们如此不同以至于 c.eval 显然无法处理类型检查的 Tree
s(相反对我来说更有意义).
What does it mean for a Tree
to be typechecked? Why are they so different that apparently c.eval can't deal with typechecked Tree
s (the inverse would make more sense to me).
我猜这可能是编译器 101,但我没有上那门课:(任何解释或指向文章/文档的指针将不胜感激!
I guess this is probably compiler 101, but I didn't take that course :(Any explanation or pointer to articles/documenation would be appreciated!
推荐答案
理论部分
这是 scalac 的架构特性,一旦我们在 2.10 中的编译时/运行时反射中公开内部编译器数据结构,它就开始泄漏到公共 API 中.
This is an architectural peculiarity of scalac that started leaking into the public API once we exposed internal compiler data structures in compile-time / runtime reflection in 2.10.
粗略地说,scalac 的前端由一个解析器和一个类型器组成,两者都与树一起工作并产生树作为结果.然而,这些树的属性完全不同,这是因为解析器生成的树是无属性的(将它们的 symbol
字段设置为 NoSymbol
和它们的 tpe
字段设置为 null
),而 typer 生成的树是属性的.
Very roughly speaking, scalac's frontend consists of a parser and a typer, both of which work with trees and produce trees as their result. However properties of these trees are quite different, which comes from the fact that trees produced by parser are unattributed (having their symbol
field set to NoSymbol
and their tpe
field set to null
), whereas trees produced by typer are attributed.
现在您可能想知道这有什么不同,因为它只是 symbol
和 tpe
,对吗?然而,在 scalac 中,它不仅仅如此.为了完成它的工作,typer 改变了它正在处理的 AST 的结构,破坏了一些原始树并产生了一些合成树.不幸的是,有时这些转换是不可逆的,这意味着如果对树进行类型检查,然后删除所有分配的属性,则生成的树将不再有意义(https://issues.scala-lang.org/browse/SI-5464).
Now you might wonder what difference this can make, because it's just symbol
and tpe
, right? However, in scalac it's more than just that. In order to do its job, typer changes the structure of the ASTs it's processing, destroying some original trees and producing some synthetic trees. Unfortunately, sometimes these transformations are irreversible, which means that if one typechecks a tree, and then erases all assigned attributes, the resulting tree isn't going to make sense anymore (https://issues.scala-lang.org/browse/SI-5464).
好吧,但是为什么要擦除(或用 Scalac 的说法,重置,如 resetLocalAttrs
或 resetAllAttrs
中的)类型检查树的属性?嗯,这种必要性源于另一个实现细节 - 符号及其所有者链.就在几天前,我在 scala-internals 上写了一些关于它的细节:https://groups.google.com/d/msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ,但简而言之,您不能在某些词法上下文中对树进行类型检查,然后简单地在不同的词法上下文中使用它(这是 c.eval
本质上需要的).
Allright, but why would one want to erase (or in scalac parlance, reset, as in resetLocalAttrs
or resetAllAttrs
) attributes of typechecked trees? Well, this necessity stems from another implementation detail - symbols and their owner chains. Just a few days ago I've written up some details about that on scala-internals: https://groups.google.com/d/msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ, but in a nutshell you can't typecheck a tree in some lexical context and then simply use it in a different lexical context (that's what is essentially needed for c.eval
).
所以,总结一下 scalac 树管理的最新技术:
So, to sum it up the state of the art in scalac tree management:
- 无类型树(也称为解析树或无属性树)在观察上不同于类型树(也称为类型树、类型检查树或属性树)
- 这两种树风格之间有两个主要区别:a) 类型树具有由类型检查器设置的符号和类型,b) 类型树的形状略有不同.
- 通常,如果某些编译器 API 采用一棵树,那么无类型树和有类型树都可以.但是,在某些情况下(我在上面概述了其中一种情况),只有无类型或只有类型的树才是合适的.
- 通过调用
Context.typecheck
(编译时反射)或ToolBox.typecheck
(运行时反射),可以从无类型树转为有类型树,但是由于 .
- Untyped trees (also known as parser trees or unattributed trees) are observationally different from typed trees (also known as typer trees, typechecked trees or attributed trees)
- There are two main differences between these two tree flavors: a) typed trees have symbols and types set by the typechecker, b) typed trees have somewhat different shapes.
- Usually, if some compiler API takes a tree, then both untyped and typed trees will do. However in some cases (one of which I outlined above), only untyped or only typed trees are appropriate.
- One can go from an untyped tree to a typed tree by calling
Context.typecheck
(compile-time reflection) orToolBox.typecheck
(runtime reflection), but going back from a typed tree to an untyped tree viaresetLocalAttrs
orresetAllAttrs
is currently unreliable because of https://issues.scala-lang.org/browse/SI-5464.
因此,如您所见,我们的树非常反复无常,这给 Scala 中的元编程带来了极大的复杂性.
So, as you can see, our trees are quite capricious, which brings a great deal of complexity into metaprogramming in Scala.
然而,好消息是,这种复杂性并不是由源自编译器 101 的一些根本原因决定的.所有的复杂性都是偶然的,我们计划逐步将其驱逐,直到它全部消失.https://groups.google.com/forum/#!topic/scala-internals/TtCTPlj_qcQ(也在几天前发布)是朝这个方向迈出的第一步.请继续关注今年也可能会推出的其他好东西!
However, good news is that this complexity isn't dictated by some fundamental good reasons that originate in compiler 101. All the complexity is incidental, and we plan to evict it step by step until it's all gone. https://groups.google.com/forum/#!topic/scala-internals/TtCTPlj_qcQ (also posted a couple days ago) is the first step in this direction. Stay tuned for other goodies which might also arrive this year!
实用部分
在详细阐述所有细节并暗指无效时的神秘案例将您彻底吓倒之后,我想指出通常人们在使用宏时不需要了解这种东西.通常,无类型树(手动构造的 AST、准引号)和类型树(宏参数)都可以正常工作.
After thoroughly scaring you by elaborating all the details and alluding to mysterious cases when nothing works, I would like to note that usually one doesn't need to know about this kind of stuff when using macros. Very often both untyped trees (manually constructured ASTs, quasiquotes) and typed trees (macro arguments) work just fine.
在某些情况下,当 scalac 需要特定的树风味时,它要么像 c.eval
一样告诉您,要么有时会当面崩溃(RefChecks、LambdaLift 和 GenICode 崩溃是树木混合的重要指标在宏扩展期间向上 - 在这些情况下使用 resetLocalAttrs
如 https://groups.google.com/forum/#!msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ).解决这个问题是我的首要任务,我现在正在努力.修复程序可能会使其成为 2.11.0,而这个答案很快就会过时:)
In cases, when scalac wants a particular tree flavor, it would either tell you like c.eval
or sometimes crash in your face (RefChecks, LambdaLift and GenICode crashes are huge indicators that trees got mixed up during macro expansion - in those cases use resetLocalAttrs
as described in https://groups.google.com/forum/#!msg/scala-internals/rIyJ4yHdPDU/qS-7DreEbCwJ). Fixing this is my top priority, and I'm working on it right now. It might so happen that the fixes will make it into 2.11.0, and this answer will become obsolete very soon :)
这篇关于Scala 宏:类型化(又名类型检查)和非类型化树之间有什么区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!