gpt4 book ai didi

macros - 这种自由项变量错误(在宏展开时产生)可以避免吗?

转载 作者:行者123 更新时间:2023-12-01 17:03:40 26 4
gpt4 key购买 nike

我正在开发 DSL,但在扩展宏时遇到“自由术语”失败。我想知道是否可以避免。我把问题简化为以下情况。

假设我们有这个表达式:

val list = join {
0
1
2
3
}
println(list)

其中 join 是一个宏,其实现是:

def join(c: Ctx)(a: c.Expr[Int]): c.Expr[List[Int]] = {
import c.mirror._
a.tree match {
case Block(list, ret) =>
// c.reify(List(new c.Expr(list(0)).eval,
// new c.Expr(list(1)).eval,
// new c.Expr(list(2)).eval) :+ new c.Expr(ret).eval)
c.reify((for (expr <- list) yield new c.Expr(expr).eval) :+ new c.Expr(ret).eval)
}
}

该宏的目的是连接参数 block 中的所有元素并将它们返回到单个列表中。由于 block 的内容可能是可变的,因此我无法使用带注释的 reify (效果很好)。未注释的 - 带有 for 理解,生成自由术语 - 抛出消息:

“宏扩展包含由 Macros.scala:48:18 中的 join 定义的自由项变量列表。将此变量拼接到 reifee 时是否忘记使用 eval?如果您在跟踪自由项变量时遇到问题,请考虑使用-Xlog-free-terms”

有没有什么方法可以引入 for 理解(或迭代器或其他)而不会出现此错误?顺便说一下,我使用的是2.10-M3。

最佳答案

问题在于您的代码混合了编译时和运行时概念。

您使用的“列表”变量是一个编译时值(即它应该在编译时迭代),并且您要求 reify 将其保留到运行时(通过拼接派生值) )。这个跨阶段的难题导致了所谓的自由术语的产生。

简而言之,自由术语是引用早期阶段的值的 stub 。例如,以下代码片段:

val x = 2
reify(x)

将编译如下:

val free$x1 = newFreeTerm("x", staticClass("scala.Int").asTypeConstructor, x);
Ident(free$x1)

聪明吧?结果保留了 x 是 Ident 的事实,保留了其类型(编译时特征),但仍然引用了它的值(运行时特征)。这是通过词法作用域实现的。

但是,如果您尝试从宏扩展(内联到宏的调用站点)返回这棵树,事情就会崩溃。宏的调用站点很可能在其词法范围内没有 x,因此它无法引用 x 的值。

更糟糕的是。如果上面的代码片段写在宏内,则 x 仅在编译时存在,即在运行编译器的 JVM 中。但是当编译器终止时,它就消失了。

但是,包含对 x 的引用的宏扩展结果应该在运行时运行(很可能在不同的 JVM 中)。为了理解这一点,您需要跨阶段持久性,即能够以某种方式序列化任意编译时值并在运行时反序列化它们。我不知道如何用 Scala 这样的编译语言来做到这一点。

<小时/>

请注意,在某些情况下,跨阶段持久性是可能的。例如,如果 x 是静态对象的字段:

object Foo { val x = 2 }
import Foo._
reify(x)

那么它最终不会成为一个自由术语,而是会以一种简单的方式具体化:

Select(Ident(staticModule("Foo")), newTermName("x"))

这是一个有趣的概念,SPJ 在 2012 年 Scala Days 的演讲中也对此进行了讨论:http://skillsmatter.com/podcast/scala/haskell-cloud

为了验证某些表达式不包含自由项,在 Haskell 中,他们向编译器添加了一个新的内置基元,即 Static 类型构造函数。对于宏,我们可以通过使用 reify(它本身只是一个宏)自然地做到这一点。请参阅此处的讨论:https://groups.google.com/forum/#!topic/scala-internals/-42PWNkQJNA .

<小时/>

现在我们已经了解了原始代码到底存在什么问题,那么我们如何使其正常工作呢?

不幸的是,我们将不得不退回到手动 AST 构建,因为 reify 很难表达动态树。 reify 在宏观学中的理想用例是拥有一个静态模板,其中包含宏编译时已知的漏洞类型。退一步 - 你将不得不手动 build 树木。

底线是您必须遵循以下内容(适用于最近发布的 2.10.0-M4,请参阅 scala-language 上的迁移指南以了解到底发生了什么变化: http://groups.google.com/group/scala-language/browse_thread/thread/bf079865ad42249c ):

import scala.reflect.makro.Context

object Macros {
def join_impl(c: Context)(a: c.Expr[Int]): c.Expr[List[Int]] = {
import c.universe._
import definitions._
a.tree match {
case Block(list, ret) =>
c.Expr((list :+ ret).foldRight(Ident(NilModule): Tree)((el, acc) =>
Apply(Select(acc, newTermName("$colon$colon")), List(el))))
}
}

def join(a: Int): List[Int] = macro join_impl
}

关于macros - 这种自由项变量错误(在宏展开时产生)可以避免吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11029675/

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