现代编程语言终极测评:二星篇
神译局是36氪旗下编译团队,关注科技、商业、职场、生活等领域,重点介绍国外的新技术、新观点、新风向。
编者按:编程语言哪种好?这可能是许多学习编程人员甚至是外行人员都会面对的头疼问题。网络上普遍的编程语言介绍,大多都是东拼西凑的内容,并且无法让人真正认识和了解各种语言的优缺点。这篇文章,原标题是These Modern Programming Languages Will Make You Suffer,作者Ilya Suzdalnitski在文章中针对15种编程语言展开了详细测评,希望对你有所帮助。
图片来源:morioh
懒人目录
概述篇:编程语言最重要的特征
一星篇:C++,JAVA
二星篇:C#,Python,Rust,TypeScript
三星篇(上):Go,JavaScript
三星篇(下):Haskell,OCaml,Scala
四星篇:Elm,F#
五星篇:ReasonML,Elixir
C#
从根本上来讲,C#和Java几乎没什么不同,因为C#的早期版本其实是Java的Microsoft实现。
C#和Java的大多数优点相同,C#首次出现在2000年,比Java晚5年,也从Java的经验中吸取了一些教训。
所属的编程语系:C
👌语法
C#语法已经比Java领先一点了,它从样板代码中遭遇的麻烦会比Java少一些。虽然C#是一门OOP语言,但是它更冗长。值得庆幸的是,它在每个发布版本中都有进步,比如添加了表达式形式的成员函数、模式匹配和元组等。
👎面向对象语言
就像Java,C#主要关注OOP。同理,我不会花时间试图让你相信OOP的缺点,我只会简单地引用计算机科学领域的一些著名人物的观点。
我认为面向对象语言缺乏可重用性,但函数性语言有可重用性。面向对象语言存在的问题是,它会提供它所拥有的所有隐性环境。打个比方,你只想要一只香蕉,但是你却得到了一个拿着香蕉的猩猩和一整片丛林。
——Erlang语言之父乔·阿姆斯特朗(Joe Armstrong)
我同意阿姆斯特朗的说法,对比函数式代码(或者命令式代码),重用面向对象的代码十分困难。
面向对象编程可以作为正确程序的替代品。
——计算机科学的先驱人物艾兹格·W·迪科斯彻(Edsger W. Dijkstra)
我用过OOP语言,也用过非OOP编程语言。我认为,相比于非OOP语言,要写对OOP语言,似乎更有难度。
👎多范式?
C#声称是多范式编程语言,它声称可支持函数式编程。我并不赞同这个观点,因为仅仅支持一流函数,并不是成为函数式语言的充要条件。
如果一个语言要自称拥有函数式特征,那么它至少需要为不可变的数据结构、模式匹配、函数组合中的管道操作符,以及代数数据类型提供内置支持。
👎并发性
C#是在单核计算时代设计出来的,它就像Java一样,只有基础的并发性支持。
👎空值
在C#中,所有的引用都可以为空。
👎错误处理
捕获错误或抛出错误是其首选的错误处理机制。
👎不可变性
没有为不可变性数据结构提供内置支持。
结论
在我职业生涯中,我很长时间都在用C#编程,但是我的感受并不太好。有许多现代编程语言都比C#更好,C#最底层的机制和Java相同,只是多了一些现代语法。
总之,非常遗憾的是,C#并没有突出的亮点。
Python
Python是在1991年设计出来的一门古老语言。Python和JavaScript一样,都是世界上最流行的编程语言之一。
所属的编程语系: C
👍生态系统
Python有一个几乎支持一切的库,虽然它不像JavaScript那样能够用来做前端网页开发,但是它可以用来构建很多数据科学库。
👍学习时需要付出的代价
Python是一门很简单的语言,新手开发者花几周时间就能入门。
👎类型系统
Python是一门动态类型化语言。其余的就没什么好说的了。
👎速度
Python是一门解释型语言,由于它极差的运行时性能,它是世界上出了名的最慢编程语言之一。当运行时性能十分重要时,可以尝试用Cython代替一般的Python。
对比本机语言,Python启动速度也很慢。
👎工具
在使用了Python以及其他现代编程语言后,我很难不对Python的依赖管理感到失望。Python的依赖管理工具有pip、pipenv、virtualenv以及pip freeze等,而JavaScript只有NPM。
👎并发性
Python在创建时并没有考虑到并发性,它现在只能实现基础的并发性支持。
👎空值
Python中,所有的引用都可以为空。
👎错误处理
捕获错误或抛出错误是其首选的错误处理机制。
👎不可变性
没有为不可变性数据结构提供内置支持。
结论
很不幸的是,Python无法为函数式编程提供适当的支持。函数式编程十分适合用来解决数据科学问题。即使是网络爬虫这样非常Python化的任务,更加适合的也可能是函数式语言(比如Elixir)。
我不推荐在大项目中使用Python,因为这门语言在创建时没怎么为软件工程考虑。
除了应用于数据科学,Python几乎没有其他用处。甚至在解决数据科学问题的时候,我们也可以使用Julia语言当作替代方案,不过Julia语言的生态系统不如Python成熟。
Rust
Rust是一门现代底层语言,它最初是作为C++的替代品而设计出来的。
所属的编程语系: C
👍速度
Rust一开始就设计得很快。Rust程序的编译花费时间长于Go程序,但Rust程序的运行时性能比Go快一些。
👍空值
Rust是本文语言目录上第一门实现了可替代空值的现代编程语言。Rust没有null或nil,开发者都使用Option模式来替代。
// Source: https://doc.rust-lang.org/rust-by-example/std/option.html
// The value returned is either a Some of type T, or None
enum Option<T> {
Some(T),
None,
}
// An integer division that doesn't `panic!`
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
if divisor == 0 {
// Failure is represented as the `None` variant
None
} else {
// Result is wrapped in a `Some` variant
Some(dividend / divisor)
}
}
// This function handles a division that may not succeed
fn try_division(dividend: i32, divisor: i32) {
// `Option` values can be pattern matched, just like other enums
match checked_division(dividend, divisor) {
None => println!("{} / {} failed!", dividend, divisor),
Some(quotient) => {
println!("{} / {} = {}", dividend, divisor, quotient)
},
}
}
👍错误处理
Rust用一种现代函数式方法进行错误处理,并用一个专门的Result数据类型来表明一个可能出错的操作,Result和上面提到的Option非常相似,但是None在Result里面也是有值的。
// The Result is either the Ok value of type T, or an Err error of type E
enum Result<T,E> {
Ok(T),
Err(E),
}
// A function that might fail
fn random() -> Result<i32, String> {
let mut generator = rand::thread_rng();
let number = generator.gen_range(0, 1000);
if number <= 500 {
Ok(number)
} else {
Err(String::from(number.to_string() + " should be less than 500"))
}
}
// Handling the result of the function
match random() {
Ok(i) => i.to_string(),
Err(e) => e,
}
👎内存管理
Rust是这个名单上唯一一门没有垃圾回收的现代语言。因此,开发者只能使用低级的内存管理。与此同时,开发者的生产力也会大幅下降。
👎并发性
由于缺乏垃圾回收,要用Rust实现并发十分困难。开发者必须考虑boxing和pinning这样的事,而这些在支持垃圾回收的语言中都无须额外考虑。
👎不可变性
Rust没有为不可变性数据结构提供内置支持。
👎底层语言
因为Rust是一门底层语言,所以开发者的生产效率会比其他更高级语言的生产效率更低,这也使得开发者需要付出更多努力来掌握它。
结论
Rust十分适合系统编程。虽然Rust比Go更加复杂,但是它提供了很好的类型系统。Rust还提供了空值的现代替代版,和一种更为现代的错误处理方式。
Rust之所以排名比TypeScript和JavaScript低,是因为它是一门低级的、为系统编程设计的语言。Rust不太适合后端或Web API开发。它缺少垃圾回收,也没有为不可变性提供内置支持。
TypeScript
TypeScript是一门会被编译为JavaScript的语言。它的主要目标是向JavaScript中添加静态类型,并做出JavaScript的升级版。就像JavaScript一样,TypeScript可用在前端开发和后端开发中。
TypeScript是由计算机科学家安德斯·海尔斯伯格(Anders Hejlsberg)设计的,他同时也是设计C#的那个人。TypeScript让人感觉像是升级版的C语言,我们可以把它看作是为浏览器设计的C#。
所属的编程语系: C
👎JavaScript的超集
因为很多人都已经熟知JavaScript了,所以作为JavaScript的超集,TypeScript很有知名度。
然而,这可能更是TypeScript的缺点。因为这意味着TypeScript拥有JavaScript的所有包,TypeScript会被JavaScript中的所有糟糕设计所限制。
举个例子,你认为有多少人会喜欢“this”这个关键字呢?我想可能没人吧。但是,TypeScript却故意地保留了这个关键字。
类型系统有时还会表现得很奇怪:
[] == ![]; // -> true
NaN === NaN; // -> false
换句话讲,TypeScript拥有JavaScript的所有缺点。一门坏的语言的超集不可能变得很好。
👍生态系统
TypeScript拥有JavaScript巨大的完整生态系统,这是它的优点。使用Node.js包管理器(NPM)工作真的很快乐,特别是和Python等语言作比较时。
其缺点是,并不是所有JS的库(比如Rambda 和Immutable.js)都有可用的TypeScript声明。
👍类型系统
我不是特别看好TypeScript的类型系统,它只是还行的程度。
从好的方面来讲,TypeScript支持代数数据类型(可辨识联合):
// Taken from: https://stackoverflow.com/questions/33915459/algebraic-data-types-in-typescript
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
我们来看看ReasonML语言中实现的同一段代码:
type shape =
| Square(int)
| Rectangle(int, int)
| Circle(int);
let area = fun
| Square(size) => size * size
| Rectangle(width, height) => width * height
| Circle(radius) => 2 * pi * radius;
TypeScript语法并不像函数式语言那么好,可辨识联合是后面加入到TypeScript 2.0中的。在switch-case语句中,我们在匹配易出错的字符串,而当我们错过一个case时,编译器不会提醒我们。
TypeScript只提供基础的类型接口,当使用它时,我们会发现,我们会很频繁使用any。
👌空值
TypeScript 2.0已经为non-nullable类型提供了很多支持,我们可以选择使用编译器标记——strictNullChecks启用它。不过,在TypeScript中使用non-nullable类型编程并不是默认的,这样使用也不太符合习惯。
👎错误处理
TypeScript中,我们使用抛出异常和捕获异常来处理错误。
👎新的JS特点
JavaScript 比TypeScript更早得到一些新的冷门的特性支持。使用Babel编译JavaScript 程序时,JavaScript甚至能够启动一些实验特性,这对于TypeScript来说,是不能做到的事情。
👎不可变性
在TypeScript中处理不可变性数据结构比JavaScript 更糟糕。因为JavaScript开发者可以使用库来处理不可变性,而TypeScript的开发者必须依赖本机数组或者对象的拓展运算符(写入时复制):
const oldArray = [1, 2];
const newArray = [...oldArray, 3];
const oldPerson = {
name: {
first: "John",
last: "Snow"
},
age: 30
};
// Performing deep object copy is rather cumbersome
const newPerson = {
...oldPerson,
name: {
...oldPerson.name,
first: "Jon"
}
};
遗憾的是,本机的拓展运算符并不能进行深度拷贝,而手动进行对象的深度拓展异常麻烦。拷贝大量的数组和对象也不利于代码的性能。
TypeScript中的关键字readonly特别好用,它可以使属性不可变。不过,要获得不可变性数据结构的支持还有很长一段路要走。
JavaScript 有一些处理不可变性数据的库,比如Rambda和Immutable.js。但是,在TypeScript中使用这些库会比较棘手。
👎TypeScript与React —— 完全不配?
在JavaScript 和TypeScript中处理不可变数据,要比用专门处理不可变数据的语言(比如Clojure)难得多。
——摘自React文档
继续讨论前面提到的缺点。如果你正在做web前端开发,那么你很有可能会使用React。
React不是专为TypeScript设计的,它一开始是为了函数式语言制造出来的(之后会详细谈论这一点),所以React和TypeScript之间存在编程范式上的矛盾——TypeScript是推崇OOP的,而React推崇函数式编程。
React希望它的属性(也就是函数参数)是不可变性的,而TypeScript不为不可变性数据结构提供合适的内置支持。
就React开发而言,TypeScript相比JavaScript 的唯一优点就是,至少在使用TypeScript编程时,我们不必为PropType操心。
TypeScript还是Hypescript?
TypeScript只是一次炒作吗?这要看你怎么想了。我认为这就是一场炒作。TypeScript最大的优点就是获取了JavaScript 的整个生态系统。
那么,为什么HypeScript如此受欢迎呢?这和Java、C#受欢迎的原因是一样的——HypeScript得到了数十亿公司的支持,并拥有巨额的营销预算。
结论
虽然TypeScript被很多人认为是更好的JavaScript ,但是我觉得它的排名比JavaScript 更低。TypeScript所提供的高于JavaScript 的优点完全被高估了,特别是对于使用React的web前端开发而言。
TypeScript保留了JavaScript 所有的糟糕部分,因此没有什么突破性改进。事实上,TypeScript几乎继承了JavaScript 几十年来所有的糟糕设计。
延伸阅读:
译者:俊一















