译注:看到这篇文章时发现不论在国内还是国外的网站上都有很多人在争论,很有意思。不过令人汗颜的是有些同学似乎只看了标题就开始吵了起来,完全没理解作者的真正意图。如果有兴趣,请耐心看完。
=======
一门不能改变你编程的思维方式的语言是不值得学习的--艾伦·佩利(Alan Perlis)
CoffeeScript是一种能够将你用特定规则书写出的文本转换成另一些文本的语言,而转换后的文本恰巧就是JavaScript源代码。它经常被称为能够将一种编程语言“编译”成JavaScript,就像将其它语言编译成汇编或者JVM字节码。像许多新事物一样,CoffeeScript从最初一些拥趸那里获得的是热情,从实用主义者那里得到的是沮丧,从固步自封的人们那里得到的是鄙视。
在文章 A Case Against Using CoffeeScript中,Ryan Florence声称那些写出差劲的CoffeeScript的人最终会得到更差劲的JavaScript程序。坦诚地说,他说得没错,自然情况下会生成一些糟糕的javaScript代码。就像Sturgeon(译注:美国科幻小说作家)评论说:“任何事物中的90%都是垃圾”。但是CoffeeScript生成的90%的垃圾代码就一定更糟糕吗?我可不这么想。
注意,我并不认为这90%的代码会奇迹般地从垃圾变为金子,垃圾就是垃圾。但是我确信它会比用“纯”JavaScript手工写出的垃圾代码要好,而且其余10%比较好的代码由于是用CoffeeScript转换来的也会更加得好。
我这样想的原因源于CoffeeScript“不是一门值得学习的语言”:
CoffeeScript就是JavaScript
我声称CoffeeScript不是一门值得学习的语言是由于
CoffeeScript不是一门语言。CoffeeScript就是JavaScript。你并不会“用CoffeeScript来思考”,你“用JavaScript来思考”,你需要考虑的只是精心设计的JavaScript。
很明显,CoffeeScript有一套不同的语法,但这只是在很表面的一层上。如果将JavaScript比作英语的话,那CoffeeScript不会是另一门语言,如法语,不会是一种方言,如牙买加土话,它将如同一名程序员与另一名之间的技术对话。
CoffeeScript并没有引入明显的新方式来组织诸如continuations、promises或者monads之类的程序。所有的转换都是本地的:如果你观察一小段CoffeeScript代码就能看出,它被转换为一小段JavaScript代码而并没有显著影响到程序的其他任何部分。没有办法能够让“编译”过的CoffeeScript同编译过的JavaScript来对话。
你写出
@render()而不是
this.renden()。了不起!这是速记符,不是语言。或者你写:
if foo and @get('bar')
doThis()
doThat()
而不是:
if (foo && this.get('bar')) {
doThis();
doThat();
}
我们如何才能让这工作呢?CoffeeScript有很多独到的精细的转换,像推导式,非结构化赋值,不定长参数,或者被一些人咒骂的“胖箭头”。理解这些的秘密是:所有这些都并非“语言特性”,并非从一种拥有它们的语言-CoffeeScript编译为一种没有它们的语言-JavaScript。
我从另一个角度看这些特性:它们是JavaScript设计模式。不要将CoffeeScript看作是一种语言,编译出一些神秘的JavaScript代码,我将生成的JavaScript代码看作是大量标准的设计模式的集合。这就是我如何写循环的,这就是我如何写带有默认参数的函数的,这就是我如何写带有固定this的函数的,这就是我如何写面向对象的类的,这就是我如何在方法中调用super()的。
CoffeeScript生成的JavaScript还是JavaScript,从好的一面想。使用一致的、标准的方式来解决我碰到的JavaScript的常见问题,再好不过的是,其他使用CoffeeScript的人也都是用完全相同的方式来解决同样的问题。
想象一下,如果你愿意,Java人接管了JavaScript。他们会做些什么?嗯,他们会就一套设计模式达成一致,他们将设计一个IDE来自动生成设计模式的骨架。因此,你会写出一些像Katy combinators库的代码来:
class OneTimeWrapper
constructor: (@what) ->
K: (fn, args...) ->
functionalize(fn)(@what, args...)
@what
T: (fn, args...) ->
functionalize(fn)(@what, args...)
chain: -> new MonadicWrapper(@what)
value: -> @what
IDE会自动将其展开为:
OneTimeWrapper = (function() {
function OneTimeWrapper(what) {
this.what = what;
}
OneTimeWrapper.prototype.K = function() {
var args, fn;
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
functionalize(fn).apply(null, [this.what].concat(__slice.call(args)));
return this.what;
};
OneTimeWrapper.prototype.T = function() {
var args, fn;
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
return functionalize(fn).apply(null, [this.what].concat(__slice.call(args)));
};
OneTimeWrapper.prototype.chain = function() {
return new MonadicWrapper(this.what);
};
OneTimeWrapper.prototype.value = function() {
return this.what;
};
return OneTimeWrapper;
})();
我将CoffeeScript看做是一个始终可编辑的大号的TextMate snippet。—@topfunky
这并不神秘,这就是最初我应该写出的JavaScript代码。很像1970年代写结构化代码用GOTO一样,聪明地使用它,避免易犯错误的结构。
因此对我来说,写CoffeeScript就是写JavaScript,只是用了一个用速记符来帮我遵循标准设计模式的工具。不像IDE,我可以在任何时候回去修改CoffeeScript代码本身,然后它帮我生成另一个JavaScript文件。
当然,我可以选择不用CoffeeScript,但我可能就写不出这么好的JavaScript。是的,如果我写出糟糕的CoffeeScript,我也会得到糟糕的JavaScript,不过,这也是在标准设计模式基础上的糟糕。
我可以,比如说,让OOP back-to-front.但是至少我是在正确的道路上做错误的事情。
CoffeeScript不是语言,是JavaScript的编码标准
总结一下我的观点,“CoffeeScript”不是一门新的编程语言,是使用标准设计模式来写JavaScript的一套缩写。生成的JavaScript不是过度优化的意大利细面条,而是JavaScript。这是一件好事。
然而,它生成的代码与我反过来写的并不相同,这可能有些令人受伤。我为什么不能像艺术家一下用我舒服的方式来表达自己呢?
答案是这不是坏事,是好事。通过标准化如何生成类,如何写循环,等等,CoffeeScript让我更容易地阅读我团队中任何人写出的代码,或者世界上任何人任何地方这类的事情。所有CoffeeScript用户用同样的方法写循环,用同样的方法写类,我们用同样的模式因为我们用了CoffeeScript来为我们生成代码。
同样的争论存在于Python中至关重要的空格,或者在团队项目中强制性的代码lint,或者在预提交时用于代码美化的一个钩子。有人说用不同方式来写所有这些小东西是净损耗,标准的缩进,标准的OOP,标准的循环诸如此类让最终的JavaScript更容易阅读,理解和维护。
这让我们能够自由得表达我们的创造性,在确实重要的事情上,在与众不同的事情上。
CoffeeScript真是一个非常棒的工具
如果你想手写出像用CoffeeScript生成出来一样的JavaScript代码,我说去做吧。当你能够做的很好时那就放手去做吧。但是请不要开始争辩CoffeeScript是否可读或者可维护或者可调试。真的,CoffeeScript不是一种语言,它是为处理常见JavaScript问题并使用标准设计模式写出标准JavaScript代码的一种行话。
如果你看重使用工具来方便地生成书写良好的JavaScript代码,CoffeeScript是一个能够帮你写好JavaScript的好工具。
如果你看重让JavaScript使用同样的设计模式以同样的方式解决同样的问题而更易于阅读,CoffeeScript是一个能够让所有人写出好JavaScript的好工具。
CoffeeScript不是一门值得学习的语言因为它根本不是一门语言。它并不能让你用一种新的思考方式来编程。它是一个用于写一种你已经掌握的编程语言-JavaScript的工具。考虑到这一点,这真他*是一个好工具。
编后记
基于收到的反馈,我想对这篇随笔做的一些说明:
关于标题
非常明显,标题有一点欺骗性。它打出批评CoffeeScript的旗号,然后反过来表扬它。对那些自认为是被骗过来读这篇文章的人我很抱歉,这不是我的本意。我的目的就是想挑战一下自己。
事情是这样的。在twitter上的一次看似无关的交流中,@jashkenas对存在CoffeeScript专门的reddit子类表示惊讶:
哼,难道不应该直接选择JavaScript来编程吗?
这让我思考,难道CoffeeScript编程不就是JavaScript编程吗?不太可能出现CoffeeScript独有的库。当然,你可以写一本关于CoffeeScript语法的书,但最终,你写的还是JavaScript。
多亏Paul Graham,我将写随笔看作是整理思绪的一个工具,因此我打算尽快写出这篇随笔。一个能够恶搞自己来完全投入去探索某个主题的方式是选择一个极端的角度去探索,问自己如果将表盘拨到11会发生什么。比如,如果代码检视是好的,极端的代码检视就变成结对编程。或者如果对象是好的,极端的OOP就是一种所有东西,甚至控制流程、数字都是真的对象。(最后一点似乎很明显,但是在SmallTalk创建时却是一个不一般的想法。)
当然,这样一篇“极端”的随笔有利于产生想法,但是真实世界的复杂程度不是一篇随笔能够覆盖的。我真的认为CoffeeScript是一门小语言。它当然改变利你考虑JavaScript编程习惯的方式。有人评论说CoffeeScript生成的JavaScript比他手写的更加函数式,因为“书写并嵌套这么多匿名函数太麻烦了”。
那么CoffeeScript真的是一门小语言吗?是的。CoffeeScript真的是JavaScript的一种行话吗?也是。CoffeeScript有它自己的东西吗?是的。CoffeeScript编程就是JavaScript编程吗?也是的。下次,我会写一个挑战性的标题,在随笔中回答它,然后将标题改为不那么具有争议性的。
谢谢所有给予反馈的人们。
Transpilers
像Hacker News中指出的那样,有一个专门的名称来描述从一种语言编译为另一种语言:Transpiling,"Transcompiling"的缩写。
严格的定义是从一种语言到另一种的任意转换,维基百科将一种ASM转换为另一种,包括优化。现在的使用似乎主要用于将一种高级语言翻译为另外一种,只使用本地变换,因此举例说很多人并不会将最初的Haskell转换为C的实现称为transpiler,因为在这个过程中包括很多全局变换以及诸如懒求值等greenspunning特性,这些本来都不是C的语义特性。
CoffeeScript被实现为transpiler,不是compiler。
模式还是熟语(idioms)?
像reddit中指出的那样,CoffeeScript进行的很多代码转换更应该被称为编程熟语而不是设计模式。熟语和设计模式的不同在于熟语主要是关于如何实现代码,而设计模式主要关于如何组织代码。
那么CoffeeScript真的是一种不同的语言吗?
这完全取决于你是否认为语法和语义定义了一种语言。-Luke VanderHart
原文链接:
https://github.com/raganwald-deprecated/homoiconic/blob/master/2011/12/jargon.md#coffeescript-is-not-a-language-worth-learning