现代编程语言终极测评:二星篇

神译局·2021年01月12日 11:20
C#,Python,Rust,TypeScript

神译局是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 几十年来所有的糟糕设计。

延伸阅读:

译者:俊一

+1
4

好文章,需要你的鼓励

参与评论
评论千万条,友善第一条
后参与讨论
提交评论0/1000

提及的项目

查看项目库
展开更多

下一篇

私域的力量。

2021-01-12

36氪APP让一部分人先看到未来
36氪
鲸准
氪空间

推送和解读前沿、有料的科技创投资讯

一级市场金融信息和系统服务提供商

聚焦全球优秀创业者,项目融资率接近97%,领跑行业