国家高新技术企业
服务热线:400-6688-605
各种编程语言是怎么一步步进化的?
发布来源:大鹏网络
发布时间:2026-07-20 09:44

前两天看到一个帖子,有人问:”Python 这么慢,凭什么火成这样?”

很多回答:因为简单、生态好、AI时代的胶水语言……

这些说法都对,但全是果,不是因。

真正值得追问的是——Python 为什么被设计成这样?Guido van Rossum 当年做了哪些取舍,放弃了什么,才换来了今天这个”慢但好用”的局面?

同样的问题可以问所有语言:C++ 为什么搞得这么复杂?JavaScript 为什么满地都是坑?Rust 为什么逼着你和编译器吵架?Go 为什么死活不肯加泛型(后来又加了)?

把这些问题串起来,你会发现一件事——今天所有主流编程语言的样子,都不是某个天才拍脑袋的产物,而是七十年来无数次设计博弈的累积结果。

一场延续七十年的连锁反应

1957 年,IBM 的 John Backus 搞出了 FORTRAN。动机很朴素:用汇编写科学计算太痛苦了,得让人写公式一样写代码。FORTRAN 证明了”高级语言不比手写汇编慢多少”这件事,从此打开了潘多拉盒子。

但 FORTRAN 的代码结构是一团乱麻——GOTO 满天飞,变量作用域混沌,写到几千行就没人看得懂了。

于是 ALGOL 出来了。ALGOL 说:代码应该有块结构,应该有词法作用域,应该长得像数学一样优雅。ALGOL 自己没怎么流行,但它的设计思想渗透进了后来几乎所有语言。你今天写的花括号 {},缩进块,局部变量——根都在 ALGOL 那里。

C 语言呢?Dennis Ritchie 在贝尔实验室写 UNIX,需要一种”能摸到硬件但又不至于是汇编”的语言。BCPL 太粗糙,B 语言没有类型系统,于是 C 诞生了。C 的哲学极其克制:信任程序员,不做多余的事。 指针随便用,内存自己管,数组越界不检查——这在当年是优点,到了四十年后变成了无数安全漏洞的根源。

C++ 是 Bjarne Stroustrup 的回答。他在剑桥读博时用 Simula 写分布式系统的模拟程序,觉得面向对象好用但 Simula 太慢;换成 C 写,性能上来了但代码结构乱得一塌糊涂。“我想要 Simula 的抽象能力,和 C 的运行速度”——C++ 就是这句话的产物。后来 C++ 越长越大,模板元编程、多继承、RAII、移动语义……一层一层往上堆。不是 Stroustrup 贪心,是真实工程需求在不断逼迫语言进化。

然后 Java 出来了。James Gosling 看着 C++ 那些让人头疼的东西——手动内存管理、头文件、未定义行为——说不干了,我要垃圾回收,要虚拟机,要”写一次到处跑”。Java 砍掉了指针运算,砍掉了多继承,砍掉了运算符重载,用约束换安全。代价是什么?啰嗦,慢,以及一个 AbstractSingletonProxyFactoryBean 级别的冗长命名传统。

Python 更激进。1989 年圣诞节,Guido van Rossum 闲得无聊,觉得现有语言对非专业程序员太不友好了。他从 ABC 语言那里借来了强制缩进和简洁语法,从 C 和 Perl 那里借来了实用主义,造出了一门”读起来像伪代码”的语言。Python 的核心赌注是:开发者的时间比 CPU 的时间值钱。 三十多年后看来,他赌对了。

再后来的故事你大概听过——JavaScript 是 Brendan Eich 用十天赶工出来的,带着 Scheme 的函数式基因和 Self 的原型继承,再套一层 Java 式的语法外衣,从此成为前端世界的独裁者和全栈世界的搅局者。Go 是 Google 三位老炮(其中有 Ken Thompson,C 和 UNIX 的共同创造者)受够了 C++ 的编译速度和复杂度后造的”反 C++“。Rust 是 Mozilla 的 Graydon Hoare 决心证明”不用垃圾回收也能保证内存安全”,用所有权系统这个激进方案给出了自己的回答。

每一种语言都不是凭空蹦出来的。它是上一代语言的痛点清单加上当时硬件条件和应用场景的交叉产物。

设计就是取舍,没有免费午餐

很多人评价编程语言喜欢说”XX 语言设计得好”或”XX 语言设计得烂”。这种说法大多数时候是不公平的。

编程语言设计本质上是一道多目标优化题,而这些目标之间经常打架:

性能 vs 安全性。 C 选了性能,结果你得自己保证不写出缓冲区溢出。Rust 说两个都要,代价是编译器像个严厉的老师,你的代码写法得服从所有权规则。Java 选了安全,代价是 GC 停顿和运行时开销。

灵活性 vs 可维护性。 Python 和 Ruby 极度灵活——运行时想改类结构就改,猴子补丁随便打。小项目写着爽,大项目几十个人协作,谁知道某个方法是在哪被偷偷覆盖的?TypeScript 出来给 JavaScript 套上类型系统,本质就是在灵活性上做减法换可维护性。

简洁 vs 表达力。 Go 的语法简单到有人说”无聊”,没有泛型(很长时间内)、没有异常、没有继承。Rob Pike 说这是特性不是缺陷——”少即是多,复杂性是敌人。”C++ 走另一个极端,模板图灵完备,你几乎可以在编译期干任何事,但也几乎可以把任何同事逼疯。

这些取舍不存在标准答案。不同的时代背景、不同的应用场景、不同的设计者性格,会导向完全不同的选择。理解这一点,你看待编程语言的视角会完全不同——不再是”哪个语言最好”的宗教战争,而是”这个语言在哪些约束条件下做了什么取舍”的工程分析。

动态类型和静态类型之争:一场没有终局的拉锯

如果说编程语言领域有一场打了几十年还没分出胜负的战争,那就是动态类型和静态类型之争。

早期语言大多是静态类型的——FORTRAN、C、Pascal,你用变量之前得告诉编译器它是什么类型。这不是因为设计者喜欢麻烦人,而是当年的硬件资源太稀缺,编译器需要提前知道类型才能生成高效代码。

Lisp 是个异类。1958 年 John McCarthy 搞出来的这门语言压根不在乎变量是什么类型——一切皆列表,运行时再说。这种做法后来叫动态类型。Smalltalk 继承了这个思路,Python、Ruby、JavaScript 进一步发扬光大。

动态类型的爽感是即时的:不用写类型声明,代码短,迭代快,原型开发飞快。静态类型的好处是长期的:编译器帮你抓 bug,IDE 能给你精准的自动补全和重构,大型项目维护起来心里有底。

有意思的是,这几年两个阵营在互相靠拢。Python 3.5 加了类型注解(Type Hints),TypeScript 给 JavaScript 穿上了类型盔甲,而 Kotlin 和 Swift 这些静态类型语言把类型推导做得极好,写起来几乎感觉不到类型声明的负担。

看起来势不两立的两条路,最后走着走着居然越来越像了。 编程语言的发展经常出现这种”殊途同归”的现象——表面上的分歧背后,其实是同一组基本矛盾在不同阶段的不同表达。


今天这些语言的样子,是历史的必然还是偶然?

两者都有。

必然的部分:硬件变了,需求变了,语言就得跟着变。1950 年代内存论 KB 算,你没法搞垃圾回收;2020 年代多核是标配,你不能不考虑并发安全。从手动管理一切到自动化一切,从单机到分布式,从命令行到 Web 到移动端到 AI——每次平台变迁都会催生新语言或迫使旧语言大改。

偶然的部分:如果 Brendan Eich 当年有两个月而不是十天,JavaScript 可能不会有那么多诡异的隐式类型转换。如果乔布斯没有回归苹果力推 NeXTSTEP,Objective-C 可能早就消失了,也不会有后来 Swift 的故事。如果 Google 没有在 2014 年选 Kotlin 作为 Android 官方语言……等等,是 2017 年。但你懂我的意思——商业决策、个人偏好、历史机缘,这些”非技术因素”对语言的命运影响巨大。

最耐人寻味的是,很多当年被认为”太学术”“太超前”的设计,几十年后变成了主流标配。ML 语言在 1973 年就有了类型推导和模式匹配,这两个特性 Rust、Swift、Kotlin 到 2010 年代才广泛采用。Lisp 1958 年就有垃圾回收和函数作为一等公民,Java 1995 年才有 GC,Java 2014 年才有 lambda。Haskell 玩了几十年的 Monad 和纯函数,现在 Rust 的 Result 类型、Swift 的 Optional,隐隐都有那个影子。

编程语言的演进从来不是线性的。它更像一棵不断分叉、嫁接、偶尔回溯的大树。


了解来路,才能看清去向

说了这么多,湖北网站建设其实想表达一个观点:只学一门语言的语法是不够的。

知道 async/await 怎么写,不如理解异步模型从回调地狱到 Promise 到协程的演进脉络。知道 Rust 的所有权规则怎么用,不如理解它为什么要在编译期解决 C++ 几十年没解决的内存安全问题,以及为此牺牲了什么。知道 Go 不加异常处理是”设计决策”,不如理解 Go 的设计者从 C 和 Plan 9 一路走来,对”简单”二字有多执着。

这种理解不是靠读文档能获得的。它需要纵向——看到七十年来这些语言之间的传承和断裂;也需要横向——把同时代、同领域的语言放在一起对照,看它们面对同一个问题时做出了怎样不同的选择。