简体   繁体   English

后代类中的宏扩展

[英]Macro expansion in descendant classes

I have next class structure: 我有下一个类结构:

trait SomeType

trait Root {
  val allMySomeTypes: Seq[SomeType]
}

class Child extends Root {
  object MyType1 extends SomeType {...}
  object MyType2 extends SomeType {...}
}

And I want to initialize val allMySomeTypes as Seq of all objects extending SomeType defined in concrete class. 我想初始化val allMySomeTypes作为扩展在具体类中定义的SomeType的所有对象的Seq。 So for Child instances it would be val allMySomeTypes: Seq[SomeType] = Seq(MyType1, MyType2). 所以对于Child实例,它将是val allMySomeTypes:Seq [SomeType] = Seq(MyType1,MyType2)。

I wrote a macro for looking for objects with some base type: 我写了一个宏来查找具有一些基类型的对象:

def getMembersOfCurrentByType_impl[S](c: Context)(implicit ev: c.WeakTypeTag[S]) = {
  import c.universe._
  val seqApply = Select(reify(Seq).tree, newTermName("apply"))
  val objs = c.enclosingClass.symbol.typeSignature.declarations.collect {
    case o: ModuleSymbol if o.typeSignature <:< ev.tpe => Ident(o) //Select(c.prefix.tree, o)
  }.toList
  c.Expr[Seq[S]] {Apply(seqApply, objs)}
}

and binded it to 并绑定到

trait Root {
  val allMySomeTypes: Seq[SomeType] = macro getMembersOfCurrentByType_impl[SomeType]
}

But, obviously, I have Empty sequence in Child class because of macro expansion for base trait. 但是,显然,由于基本特征的宏扩展,我在Child类中有空序列。 Can I build Seq of actual members without extra typing in Child class and without using of runtime reflection? 我可以在没有在Child类中额外输入而不使用运行时反射的情况下构建实际成员的Seq吗?

General case of subclass detection 子类检测的一般情况

Interestingly enough, we had a similar discussion at scala-user just a few days ago, where we conversed about the possibility of listing all subclasses of a given class in general case. 有趣的是,几天前我们在scala-user上进行了类似的讨论 ,在那里我们讨论了在一般情况下列出给定类的所有子类的可能性。 Here's the answer that I posted back then: 这是我当时发布的答案:

It is currently not possible to enumerate all subclasses of an arbitrary class, but we do have an API that's called ClassSymbol.knownDirectSubclasses , which enumerates sealed descendants. 目前不可能枚举任意类的所有子类,但我们确实有一个名为ClassSymbol.knownDirectSubclasses的API,它枚举了密封的后代。

But unfortunately, even that is not easy. 但不幸的是,即便这样也不容易。 The knownDirectSubclasses API is more or less fine in the sense that people are somehow using it, but it also has a serious flaw that we currently don't know how to fix: https://groups.google.com/forum/#!topic/scala-internals/LRfKffsPxVA . knownDirectSubclasses API在人们以某种​​方式使用它的意义上或多或少都很好,但它也有一个严重的缺陷,我们目前不知道如何修复: https//groups.google.com/forum/#! topic / scala-internals / LRfKffsPxVA

Particular case of subclass detection 子类检测的特殊情况

However this StackOverflow question asks about something more specific. 但是,这个StackOverflow问题会询问更具体的内容。 How about we restrict ourselves to just the members of some class, would it then be possible to pick those that are subclasses of the class we're interested in? 我们如何将自己局限于某个类的成员,然后可以选择那些我们感兴趣的类的子类吗?

Well, it turns out that despite that there's currently an API to deal with that ( c.enclosingClass of the family of c.enclosingTree methods), even this particular case still can't be dealt with robustly yet. 好吧,事实证明,尽管目前有一个API来处理它( c.enclosingClass系列的c.enclosingTree ),即使这个特殊情况仍然无法稳健地处理。

The thing is that Scala macros are expanded during typechecking, which means that at the time when a given macro is being expanded, its enclosing trees are being typechecked. 问题是Scala宏在类型检查期间被扩展,这意味着在给定宏被扩展时,它的封闭树被强调了。 The "being typechecked" status means that some enclosing trees might temporarily be in inconsistent state that's going to break down if one tries to inspect them. “被类型检查”状态意味着一些封闭的树可能暂时处于不一致的状态,如果试图检查它们就会崩溃。 There's a more elaborate discussion at Scala Macro Annotations: c.TypeCheck of annotated type causes StackOverflowError , but here I'll give just a quick example. Scala Macro Annotations上有一个更详细的讨论:带注释类型的c.TypeCheck会导致StackOverflowError ,但在这里我只举一个简单的例子。

For instance, if your macro is expanded inside a method with an unspecified return type (eg def foo = yourMacro(1, 2, 3) ), then calling c.enclosingDef.symbol.typeSignature is going to fail macro expansion, because, well, to know the signature of the method, you need to infer its return type, but to infer the return type you need to expand the macro, for which you need to know the signature of the method, etc. By the way, the compiler is smart enough in order not to loop infinitely here - it will break the loop early and show a cyclic reference error. 例如,如果您的宏在具有未指定返回类型的方法内扩展(例如def foo = yourMacro(1, 2, 3) ),那么调用c.enclosingDef.symbol.typeSignature将失败宏扩展,因为,以及,要知道方法的签名,你需要推断它的返回类型,但要推断你需要扩展宏的返回类型,你需要知道方法的签名等。顺便说一下,编译器为了不在这里无限循环,它足够聪明 - 它会提前打破循环并显示循环引用错误。

This means that to handle non-local macro expansions well, we need some sort of a declarative abstraction over type signatures and their dependencies rather than just an imperative typeSignature method. 这意味着要很好地处理非局部宏扩展,我们需要对类型签名及其依赖关系进行某种声明性抽象,而不仅仅是命令式typeSignature方法。 I've been thinking about this on and off for the last couple of months, but so far haven't come up with anything satisfactory, which was one of the reasons why in Scala 2.11 we're going to deprecate non-local c.enclosingTree methods: https://github.com/scala/scala/pull/3354 . 在过去的几个月里,我一直在思考这个问题,但到目前为止还没有得到任何令人满意的结果,这也是为什么在Scala 2.11中我们要弃用非本地c.enclosingTree的原因之一c.enclosingTree方法: https//github.com/scala/scala/pull/3354

With the advent of macro annotations, this theme is going to become much more important, so we're not admitting defeat just yet. 随着宏观注释的出现,这个主题将变得更加重要,所以我们还没有承认失败。 I think, it is reasonable to expect advances in this area, but there are no concrete dates that we can provide for this one. 我认为,期待这一领域取得进展是合理的,但我们没有具体的日期可以提供给这一领域。

A possible workaround 一种可能的解决方法

As I mentioned above, the "only" problem that we have with the task of detecting subclasses is that it's an operation that is non-local with respect to the macro expansion that performs it. 正如我上面提到的,我们在检测子类的任务中遇到的“唯一”问题是它是一个非执行它的宏扩展操作。 So how about we turn that into a local operation? 那么我们如何将其转变为本地运营呢? It turns out that it's possible. 事实证明这是可能的。

By putting a macro annotation on the Child class, in your macro you'll be able to see all the members of the class before they are typechecked, which means that inspecting these members won't be causing potentially spurious cyclic errors. 通过在Child类上放置宏注释,在宏中,您将能够在类型检查之前看到类的所有成员,这意味着检查这些成员不会导致潜在的虚假循环错误。

However, if these members aren't typechecked, then what good are they to us? 但是,如果这些成员没有受到严格监控,那么他们对我们有什么好处呢? We need types to perform the subclass check, right? 我们需要类型来执行子类检查,对吧? Well, yes and no. 嗯,是的,不。 We indeed need types, but we don't have to get those types from the members themselves. 我们确实需要类型,但我们不必从成员本身获取这些类型。 We can take the annotated class, duplicate it, typecheck it and then inspect the results of the typecheck without the fear of disrupting the annottee. 我们可以使用带注释的类,复制它,对其进行检查,然后检查类型检查的结果,而不必担心会破坏被注意者。 Here's an example that will provide inspiration for implementing this strategy: Can't access Parent's Members while dealing with Macro Annotations . 这是一个为实施此策略提供灵感的示例: 在处理宏注释时无法访问Parent的成员

tl;dr TL;博士

  1. At the moment, non-local operations during macro expansion can be hard or impossible to perform robustly, because of both theoretical and implementational reasons. 目前,由于理论和实施原因,宏观扩张期间的非本地运营可能很难或不可能稳健地执行。 SI-7046 is one example, this question provides another one. SI-7046就是一个例子,这个问题提供了另一个例子。
  2. Sometimes it is possible to reformulate a non-local macro expansion problem as a local one by enclosing a bigger scope in a macro. 有时,通过在宏观中包含更大的范围,可以将非本地宏观扩张问题重新定位为本地宏观扩张问题。 Macro annotations provided by the macro paradise plugin might be helpful for this purpose. 宏天堂插件提供的宏注释可能有助于此目的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM