gpt4 book ai didi

scala - 如何在宏中重用定义 (AST) 子树?

转载 作者:行者123 更新时间:2023-12-04 01:42:54 25 4
gpt4 key购买 nike

我在 Scala 嵌入式 DSL 中工作,宏正在成为实现我的目的的主要工具。尝试将传入宏表达式中的子树重用到结果中时出现错误。情况相当复杂,但是(我希望)我已经简化了它以便理解。

假设我们有这样的代码:

val y = transform {
val x = 3
x
}
println(y) // prints 3

其中 'transform' 是所涉及的宏。尽管它看起来似乎什么也没做,但它确实将显示的 block 转换为这个表达式:
3 match { case x => x }

这是通过这个宏实现完成的:
def transform(c: Context)(block: c.Expr[Int]): c.Expr[Int] = {
import c.universe._
import definitions._

block.tree match {
/* {
* val xNam = xVal
* xExp
* }
*/
case Block(List(ValDef(_, xNam, _, xVal)), xExp) =>
println("# " + showRaw(xExp)) // prints Ident(newTermName("x"))
c.Expr(
Match(
xVal,
List(CaseDef(
Bind(xNam, Ident(newTermName("_"))),
EmptyTree,
/* xExp */ Ident(newTermName("x")) ))))
case _ =>
c.error(c.enclosingPosition, "Can't transform block to function")
block // keep original expression
}
}

请注意 xNam 对应变量名 xVal 对应于它的关联值,最后是 xExp 对应于包含变量的表达式。好吧,如果我打印 xExp 原始树,我会得到 Ident(newTermName("x")),这正是 RHS 案例中设置的内容。由于可以修改表达式(例如 x+2 而不是 x),因此这对我来说不是一个有效的解决方案。我想要做的是重用 xExp 树(参见 xExp 注释),同时改变 'x' 的含义(它是输入表达式中的定义,但将是输出表达式中的 case LHS 变量),但它会启动一个长错误总结在:
symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.

我当前的解决方案包括解析 xExp 以用新的标识替换所有标识,但它完全依赖于编译器内部,因此是一种临时解决方法。很明显,xExp 附带了 showRaw 提供的更多信息。如何清理该 xExp 以允许“x”扮演案例变量的角色?谁能解释这个错误的全貌?

PS:我一直在尝试使用 TreeApi 中的替代*方法系列,但没有成功。但我缺少了解其含义的基础知识。

最佳答案

分解输入表达式并以不同的方式重新组装它们是宏观学中的一个重要场景(这是我们在 reify 宏内部所做的事情)。但不幸的是,目前这并不是特别容易。

问题是宏的输入参数到达已经过类型检查的宏实现。这既是福也是祸。

我们特别感兴趣的是,与参数对应的树中的变量绑定(bind)已经建立。这意味着所有 IdentSelect节点有他们的sym填写的字段,指向这些节点所引用的定义。

这是符号如何工作的示例。我将从我的一个演讲中复制/粘贴一份打印输出(我在这里不提供链接,因为我演讲中的大部分信息现在已被弃用,但这个特定的打印输出具有永恒的用处):

>cat Foo.scala
def foo[T: TypeTag](x: Any) = x.asInstanceOf[T]
foo[Long](42)

>scalac -Xprint:typer -uniqid Foo.scala
[[syntax trees at end of typer]]// Scala source: Foo.scala
def foo#8339
[T#8340 >: Nothing#4658 <: Any#4657]
(x#9529: Any#4657)
(implicit evidence$1#9530: TypeTag#7861[T#8341])
: T#8340 =
x#9529.asInstanceOf#6023[T#8341];
Test#14.this.foo#8339[Long#1641](42)(scala#29.reflect#2514.`package`#3414.mirror#3463.TypeTag#10351.Long#10361)

回顾一下,我们编写了一个小片段,然后用 scalac 编译它,要求编译器在 typer 阶段之后转储树,打印分配给树的符号的唯一 ID(如果有的话)。

在结果打印输出中,我们可以看到标识符已链接到相应的定义。例如,一方面, ValDef("x", ...) ,代表方法foo的参数,定义了一个id=9529的方法符号。另一方面, Ident("x")在方法的主体中得到了它的 sym字段设置为相同的符号,从而建立绑定(bind)。

好的,我们已经了解了绑定(bind)在 scalac 中是如何工作的,现在是介绍一个基本事实的最佳时机。
If a symbol has been assigned to an AST node, 
then subsequent typechecks will never reassign it.

这就是为什么 reify 是卫生的。您可以获取 reify 的结果并将其插入任意树(可能定义名称冲突的变量) - 原始绑定(bind)将保持不变。这是有效的,因为 reify 保留了原始符号,因此后续类型检查不会重新绑定(bind) reified AST 节点。

现在我们都准备好解释您面临的错误:
symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.
transform 的论点宏包含变量的定义和引用 x .正如我们刚刚了解到的,这意味着相应的 ValDef 和 Ident 将有它们的 sym字段同步。到现在为止还挺好。

然而不幸的是,宏破坏了已建立的绑定(bind)。它重新创建 ValDef,但不清理 sym对应标识的字段。随后的类型检查为新创建的 ValDef 分配一个新符号,但不会触及逐字复制到结果的原始 Ident。

在类型检查之后,原始 Ident 指向一个不再存在的符号(这正是错误消息所说的 :)),这导致在字节码生成期间崩溃。

那么我们如何修复错误呢?不幸的是,没有简单的答案。

一种选择是使用 c.resetLocalAttrs ,它递归地删除给定 AST 节点中的所有符号。随后的类型检查将重新建立绑定(bind),前提是您生成的代码不会与它们混淆(例如,如果您将 xExp 包装在一个 block 中,该 block 本身定义了一个名为 x 的值,那么您就有麻烦了)。

另一种选择是摆弄符号。例如,您可以编写自己的 resetLocalAttrs只会删除损坏的绑定(bind)并且不会触及有效的绑定(bind)。您也可以尝试自己分配符号,但这是一条通往疯狂的捷径,尽管有时人们被迫走它。

一点都不酷,我同意。我们意识到这一点,并打算有时尝试解决这个基本问题。然而,现在我们的手在 2.10.0 最终版本之前进行了错误修复,所以我们无法在最近的将来解决这个问题。更新。见 https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU了解更多信息。

底线。坏事发生了,因为绑定(bind)搞砸了。先试试resetLocalAttrs,如果还是不行,就为自己做点家务吧。

关于scala - 如何在宏中重用定义 (AST) 子树?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11208790/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com