透明编译

2013年2月12日

越来越多的Web开发人员使用诸如CoffeeScriptSCSS之类的语言,这些语言编译成在浏览器中执行的其他文本源语言。这种源到源编译器(也称为转译器[1])并不新鲜,Cfront在C++的早期被广泛用于生成目标C代码。但对我来说,有一个区别将CoffeeScript和SCSS区分开来,即它们是透明编译器

对于大多数编译器,您不太关心下游生成的代码。只要它遵循源语言的语义,它实际上就是一个大块的比特。但是,如果您要为浏览器生成JavaScript,这种无知就很难忍受。如今,调试环境变得非常不错,但它们都是以HTML/CSS/JavaScript三元组为基础的。因此,了解您的输入语言如何转换为其可执行目标非常重要。

此约束对源语言有很大影响。您需要确保输出与源代码非常清楚地对应。当我编写这段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在语法上更像Python,而不是像C一样的JavaScript。这种语法差异并不总是存在,事实上,透明编译语言的一个重要子集努力成为目标语言的超集。SCSS和TypeScript属于此类 - 任何CSS表达式在SCSS中都是有效的。使用超集语言可以使对应关系更加清晰,我认为这对于CSS来说效果很好,因为CSS的语法很好,但语言缺少一些方便的功能。

有些人说使用透明编译没有意义 - 如果你必须理解目标代码才能进行调试,那么使用不同的源代码有什么价值呢?对我来说,价值在于几个方面。首先,它是一种获得目标语言中缺少的有用语言特性的方法。SCSS为我提供了方便的功能,例如变量(因此我可以说$light-purple而不是#f8c8fe,如果我想调整它,只需在一个地方更改它)。

更激进的语法更改,例如CoffeeScript,需要更强的理由。我的一位同事在完成一个项目后说得很好。他是一位经验丰富的JavaScript程序员,该项目从一开始就编写了纪律严明的JavaScript代码。因此,他对JavaScript代码库的质量非常满意。然而,他仍然得出结论,他们最好在CoffeeScript中工作,因为即使在调试生成的JavaScript代码时,在阅读CoffeeScript代码时也更容易理解发生了什么。对于像上面我展示的那些小片段来说,这种转换可能看起来并不那么重要。但一旦您达到数百行代码,甚至更多,它就会产生很大的影响。[4]

注释

1: 经过一番搜索,我发现“转译器”一词被用作源到源编译器的同义词。因此,转译器可能是透明的,也可能不是透明的。我还看到“源到源转换”一词等同于“源到源编译”。

2: 即使是对于不透明的编译,也有一些情况下人们会研究输出。有时,确实需要您深入研究编译器输出才能发现奇怪的行为或错误。一些程序员喜欢了解编译器在做什么,尽管随着编译器和虚拟机变得越来越复杂,这种做法越来越少见。但这种活动是一种例外情况。

3: 有趣的是,看看源映射的开发是否会将像coffeescript这样的语言从透明性中转移出来。

4: 我在制作信息卡时只写了几百行coffeescript代码,但我同意他的观点,并将继续在任何非平凡的javascript代码量中使用coffeescript。