6

私は非常に一般的なコード生成を可能にする Scala コンパイラ プラグインを書こうとしています: C プリプロセッサの一般性に似ていますが、もう少しタイプセーフです (これがひどいアイデアかどうかはわかりませんが、楽しい演習です)。 )。私の理想的なユースケースは次のようになります。

// User code. This represents some function that might take some args
// and outputs an abstract syntax tree.
def createFooTree(...): scala.reflect.runtime.universe.Tree = ...

// Later user code (maybe separate compilation?). Here the user generates
// code programmatically using the function call to |createFooTree| and inserts
// the code using insertTree.
insertTree(createFooTree(...))

重要なプラグイン コードは次のようになります (これに基づく):

class InsertTreeComponent(val global: Global)
  extends PluginComponent
  with TypingTransformers {
  import global._
  import definitions._

  override val phaseName = "insertTree"

  override val runsRightAfter = Some("parser")
  override val runsAfter = runsRightAfter.toList
  override val runsBefore = List[String]("typer")

  def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
    def apply(unit: CompilationUnit) {
      val onTransformer = new TypingTransformer(unit) {
        override def transform(tree: Tree): Tree = tree match {
          case orig @ Apply(
            function,
            // |treeClosure| is the closure we passed, which should
            // evaluate to a Tree (albeit a runtime Tree).
            // The function.toString bit matches anything that looks like a
            // function call with a function called |insertTree|.
            treeClosure) if (function.toString == "insertTree") => {
            // This function evaluates and returns the Tree, inserting it
            // into the call site as automatically-generated code.
            // Unfortunately, the following line isn't valid.
            eval(treeClosure): Tree
          }   
  ...

これを行う方法はありますか?「マクロを使うだけ」とは言わないでください。少なくとも 2.10 では、十分に一般的ではありません。

ところで、私が概説したアプローチには 2 つの問題があります。1) コンパイラ プラグインは、クロージャではなく AST を取ります。おそらく、ユーザー コードにビルドの依存関係を追加して、クロージャを作成する何らかの方法が必要になります。2) ユーザーは scala.reflect.internal.Trees.Tree にアクセスできず、scala.reflect.runtime.universe.Tree にしかアクセスできないため、プラグインは 2 つの間で変換する必要があります。

4

1 に答える 1

9

あなたが直面している実装上の問題は、2.10 のマクロが十分に一般的でない理由の一部です。それらは非常に挑戦的で基本的なものにさえ見えますが、私はそれらが最終的に打ち負かされる可能性があると楽観的です. 以下に、デザインに関する難しい質問をいくつか示します。

1) 呼び出している関数が正しいことをどうやって知ることができますinsertTreeか? ユーザーが名前付きの独自の関数を作成した場合はinsertTreeどうなるでしょうか。特殊関数への魔法の呼び出しとユーザー定義関数への通常の呼び出しをどのように区別しますか? 確実にするには、関数への参照を型チェックする必要があります。しかし、それは簡単なことではありません (以下を参照)。

2) 通話を正確にどのように評価しcreateFooTree(...)ますか? 前と同じように、createFooTreeパーツが何を表しているかを調べるには、そのパーツをタイプチェックする必要がありますが、これは簡単ではありません。

3) そして、もう 1 つ問題があります。createFooTree現在コンパイルしているファイルの 1 つに が定義されている場合はどうなるでしょうか。次に、それとその依存関係をプログラムの残りの部分から分離し、別のコンパイル実行に入れ、コンパイルしてから呼び出す必要があります。そして、関数またはこれらの依存関係のいずれかのコンパイルが、コンパイラのグローバルな状態を変更するはずのマクロ展開につながる場合はどうなるでしょうか。プログラムの残りの部分にどのように伝播するのでしょうか?

4) 私はいつも型チェックについて話している。問題ありますか?どうやら、はい。マクロがどこにでも展開できる場合、型チェックは非常に難しくなります。たとえば、これをどのようにタイプチェックしますか:

class C {
  insertTree(createFoo(bar)) // creates `def foo = 2`, requires `bar` to be defined to operate
  insertTree(createBar(foo)) // creates `def bar = 4`, requires `foo` to be defined to operate
}

5) ただし、良いニュースは、 を使用する必要がないことですscala.reflect.runtime.universe.TreecreateFooTree依存して型指定した可能性があります: def createFooTree[U <: scala.reflect.api.Universe with Singleton](u: Universe): u.Tree。これ、またはscala.reflect.macros.ContextScala 2.10 で使用するアプローチです。あまりきれいではありませんが、宇宙の不一致の問題を解決します。

要するに、私の現在の感覚では、静的に型付けされた言語 (特に、オブジェクト指向言語では、オブジェクト指向言語では、コードの断片が互いに依存する方法が驚くほどたくさんあるため) は非常にトリッキーだと感じています。コンパイル中のプログラムの任意のフラグメントを変更する型付きマクロの堅牢なモデルはまだ発見されていません。

ご希望があれば、メールでより詳細なディスカッションを行うこともできます。また、協力して適切なマクロのアイデアを実現することもできました。または、ユースケースを共有していただければ、特定の状況の回避策を見つけるお手伝いをすることができます.

于 2013-03-14T13:33:35.030 に答える