可以避免这种自由项变量错误(在宏扩展时产生)吗? [英] Can this free-term-variable error (produced at macro expansion) be avoided?

查看:12
本文介绍了可以避免这种自由项变量错误(在宏扩展时产生)吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发 DSL,但在扩展宏时遇到免费期限"故障.我想知道是否可以避免.我已将问题简化为以下情况.

I am developing a DSL and I am getting a "free term" failure while expanding a macro. I would like to know if it can be avoided. I have simplified the problem to the following situation.

假设我们有这个表达式:

Suppose we have this expression:

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

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

where join is a macro whose implementation is:

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)
  }
}

宏的目的是连接参数块中的所有元素并将它们返回到一个列表中.由于块的内容可能是可变的,我不能使用注释的 reify (效果很好).未注释的 - 带有 for 理解,生成自由条款 - 抛出消息:

The aim of the macro is to join all the elements in the argument block and return them in a single list. Since the contents of the block could be variable, I cannot use the commented reify (which works nice). The uncommented one -with a for comprehension, which generates free terms- throws the message:

"宏扩展包含通过在Macros.scala:48:18 中join 定义的free term 变量列表.你是否忘记在将这个变量拼接成reifee 时使用eval?如果你在跟踪free term 变量时遇到问题,考虑使用 -Xlog-free-terms"

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

Is there any way to introduce the for-comprehension (or an iterator or whatever) without getting this error? By the way, I am using 2.10-M3.

推荐答案

问题是你的代码混合了编译时和运行时的概念.

The problem is that your code mixes compile-time and runtime concepts.

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

The "list" variable you're using is a compile-time value (i.e. it is supposed to be iterated during the compile-time), and you're asking reify to retain it till the runtime (by splicing derived values). This cross-stage conundrum leads to creation of a so called free term.

简而言之,免费术语是指早期阶段的值的存根.例如,以下代码段:

In short, free terms are stubs that refer to the values from earlier stages. For example, the following snippet:

val x = 2
reify(x)

会编译如下:

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

聪明,对吧?结果保留了 x 是一个 Ident 的事实,保留了它的类型(编译时特性),但仍然引用了它的值(运行时特性).这可以通过词法作用域来实现.

Clever, huh? The result retains the fact that x is an Ident, preserves its type (compile-time characteristics), but, nevertheless, refers to its value as well (a run-time characteristic). This is made possible by lexical scoping.

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

But if you try to return this tree from a macro expansion (that is inlined into the call site of a macro), things will blow up. Call site of the macro will most likely not have x in its lexical scope, so it wouldn't be able to refer to the value of x.

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

What's even worse. If the snippet above is written inside a macro, then x only exists during compile-time, i.e. in the JVM that runs the compiler. But when the compiler terminates, it's gone.

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

However, the results of macro expansion that contain a reference to x are supposed to be run at runtime (most likely, in a different JVM). To make sense of this, you would need cross-stage persistence, i.e. a capability to somehow serialize arbitrary compile-time values and deserialize them during runtime. I don't know how to do this in a compiled language like Scala.

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

Note that in some cases cross-stage persistence is possible. For example, if x was a field of a static object:

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

那么它就不会成为一个免费的术语,而是会以一种直接的方式被具体化:

Then it wouldn't end up as a free term, but would be reified in a straightforward fashion:

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

这是一个有趣的概念,SPJ 在 2012 年 Scala 日的演讲中也讨论了这个概念:http://skillsmatter.com/播客/scala/haskell-cloud.

This is an interesting concept that was also discussed in SPJ's talk at Scala Days 2012: http://skillsmatter.com/podcast/scala/haskell-cloud.

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

To verify that some expression doesn't contain free terms, in Haskell they add a new built-in primitive to the compiler, the Static type constructor. With macros, we can do this naturally by using reify (which itself is just a macro). See the discussion here: https://groups.google.com/forum/#!topic/scala-internals/-42PWNkQJNA.

好的,现在我们已经看到原始代码到底有什么问题,那么我们如何让它工作呢?

Okay now we've seen what exactly is the problem with the original code, so how do we make it work?

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

Unfortunately we'll have to fall back to manual AST construction, because reify has tough time expressing dynamic trees. The ideal use case for reify in macrology is having a static template with the types of the holes known at macro compilation time. Take a step aside - and you'll have to resort to building trees by hand.

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

Bottom line is that you have to go with the following (works with recently released 2.10.0-M4, see the migration guide at scala-language to see what exactly has changed: 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
}

这篇关于可以避免这种自由项变量错误(在宏扩展时产生)吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆