透過的コンパイル
2013年2月12日
Web開発者は、ブラウザで実行される他のテキストベースのソース言語にコンパイルされるCoffeeScriptやSCSSのような言語をますます使用するようになっています。このようなソースツーソースコンパイラ(トランスパイラとも呼ばれます[1])は新しいものではありません。Cfrontは、C++の初期にターゲットCコードを生成するために広く使用されていました。しかし、私にとっては、CoffeeScriptとSCSSを透過的コンパイラとして際立たせる違いがあります。
ほとんどのコンパイラでは、ダウンストリームで何が生成されるかはあまり気にしません。ソース言語のセマンティクスに従っている限り、それは事実上大きなビットの塊です。しかし、ブラウザ用のJavaScriptを生成する場合、この無知は許容しがたいものです。デバッグ環境は最近かなり巧妙になっていますが、すべてHTML/CSS/JavaScriptの3つ組に基づいています。そのため、入力言語が実行可能なターゲットにどのように変換されるかを理解することが重要です。
この制約は、ソース言語に大きな影響を与えます。出力はソースに非常に明確に対応している必要があります。私がこのCoffeeScriptを書くと
$(window).on 'touchTap', (event) -> window.touchPanel.tap(event)
ブラウザのデバッガで、結果のJavaScriptを簡単に認識できます。
$(window).on('touchTap', function(event) { return window.touchPanel.tap(event); });
これは、次のようなCoffeeScriptのより複雑な変換の場合でも当てはまります。
runSetupBuild: (slide, positionClass) -> switch positionClass when 'current', 'next' @buildsFor(slide)?.setupBuild?.forwards() # ...
を
Infodeck.prototype.runSetupBuild = function(slide, positionClass) { var _ref, _ref1, _ref2, _ref3; switch (positionClass) { case 'current': case 'next': return (_ref = this.buildsFor(slide)) != null ? (_ref1 = _ref.setupBuild) != null ? _ref1.forwards() : void 0 : void 0; /* ... */
この変換には多くの処理が含まれていますが、それでも対応関係は非常に明確です。そのコードでデバッグする必要がある場合、ソースCoffeeScriptとの対応関係を簡単に確認できます。これがコンパイルプロセスを透過的にする本質です。つまり、出力言語で作業することを意図しているということです[2]。
対照的に、出力言語で作業することを想定していない、または出力言語の可視性を一時的な不幸なメカニズムと見なすソースツーソースコンパイラがあります。これらは依然として有用であり、JavaScriptの世界ではDart、GWT、ClojureScriptなどの言語で見られます。この意図の違いが、透過的なトランスパイルスタイルをより一般的なアプローチと区別するものです。[3]
コンパイルを透過的に保つために作業する必要があるという事実は、ソース言語でできることに制限を課します。より制約のない形式のコンパイルで得られる言語構成における自由度はありません。ターゲット言語の基本的なセマンティクスに従い、ほぼ同じプログラム構造を維持する必要があります。これらの制約は、言語設計の機能として広く議論されていません。
CoffeeScriptは、これらの制約にもかかわらず、ソース言語とターゲット言語の間で構文に大きな違いが生じる可能性があることを示しています。CoffeeScriptは、構文的にはC言語に似たJavaScriptよりもPythonに似ています。このような構文上のバリエーションは常に当てはまるわけではありません。実際、ターゲット言語のスーパーセットであることを目指す透過的なコンパイル言語の重要なサブセットがあります。SCSSとTypeScriptはこのカテゴリに当てはまります。CSS式はすべてSCSSで有効です。スーパーセット言語を使用すると、対応関係がさらに明確になり、CSSの構文はうまく機能するものの、言語に便利な機能がいくつか欠けているCSSには適していると感じています。
透過的コンパイルを使用する意味はほとんどないと言う人もいます。デバッグのためにターゲットコードを理解する必要がある場合、異なるソースを使用することの価値は何でしょうか?私にとって、価値はいくつかの点にあります。まず、ターゲット言語に欠けている便利な言語機能を取得する方法です。SCSSは、変数などの便利な機能を提供します(そのため、#f8c8fe
の代わりに$light-purple
と言って、微調整したい場合にのみ1か所で変更できます)。
CoffeeScriptなどのより抜本的な構文変更には、より強力な正当化が必要です。私の同僚の1人は、プロジェクトを完了した後、それを非常にうまく表現しました。彼は経験豊富なJavaScriptプログラマーであり、プロジェクトは最初から規律の取れたJavaScriptを作成しました。その結果、彼はJavaScriptコードベースの品質に非常に満足していました。しかし、彼はそれでも、CoffeeScriptで作業する方が良かったと結論付けました。生成されたJavaScriptコードでデバッグしている場合でも、CoffeeScriptを読んでいるときに何が起こっているかを理解する方が簡単だからです。変換は、上記で示したような小さなフラグメントではそれほど大きな問題には見えないかもしれません。しかし、数百行のコード、ましてやそれ以上のコードになると、大きな違いが生じます。[4]
注記
1: 使用法を調べてみると、「トランスパイラ」という用語はソースツーソースコンパイラの同義語として使用されているようです。そのため、トランスパイラは透過的である場合とそうでない場合があります。「ソースツーソース変換」という用語も「ソースツーソースコンパイル」と同等に使用されているのを見たことがあります。
2: 不透明なコンパイルの場合でも、人々が出力を調べる場合があります。コンパイラの出力内容を掘り下げる必要がある奇妙な動作やバグが時折発生します。一部のプログラマーは、コンパイラが何をしているのかを理解するのが好きですが、コンパイラと仮想マシンがより洗練されるにつれて、それはあまり一般的ではなくなりました。しかし、そのような活動は例外です。
3: ソースマップの開発が、CoffeeScriptのような言語を透過性から遠ざけるかどうかを見るのは興味深いことです。
4: 私はinfodeckの作業中に数百行のCoffeeScriptしか作成していませんが、彼に同意し、かなりの量のJavaScriptにはCoffeeScriptを使い続けます。