这么多编程语言,有没有一种可能能把他们“大一统”?

994 👍 / 65 💬

问题描述

虽然说每个编程语言都有各自的特点,而且有些甚至大相径庭,学起来很麻烦,有没有可能能让他们统一起来?


大一统人你好,既要、也要、还要、更要,大一统人再见。

编程语言说白了是一种信道,信源就是人类程序员,目标就是 CPU。

你有一个复杂的想法,但是 CPU 是个铁憨憨,只认识 0101,那咋办呢,只能通过编程语言把你脑子里的东西传给 CPU。

所以编程语言是一种信道。


为啥要有这么多语言?

因为要传输的信息种类太多了,对传输效率的要求也千差万别。

祖师爷香农说啊,信道容量 C 等于带宽乘以信噪比的对数:

 C = B \log_2 \left(1 + \frac{S}{N}\right)


那把编程语言看成是一个信道,有效信息 S 是什么?

是那些直接服务于计算任务的指令,是真正干活的机器码。

那么噪声 N 是什么?

噪音就是所有为了管理对象、为了安全、为了动态特性额外增加的机制。

什么对象扫描、GC回收、动态类型检查、虚拟机指令转译、各种运行时检查开销。

一个语言噪音越大,它的信噪比 S/N 就越低,它的理论性能天花板 C 就越低。


那像C语言这种,它的设计哲学就是极致降噪。

几乎把所有的噪声源都给干掉了,没有GC,没有VM,没有自动边界检查。

没有任何脏东西,那这就是一种纯净的信道,信噪比极高,所以它的性能天花板 C 就非常接近硬件的物理极限。

那么古尔丹,代价是什么呢?

代价是所有的降噪工作都得你这个程序员自己来做。

你就是个人肉降噪器,你得手动管理内存控制S,你得自己保证别越界来控制N

你一旦没做好,那程序就跑飞了,这叫信道污染,信息就全废了。


那 Python 和 JavaScript 这种呢?

它们往信道里塞了一大堆看起来有用实际没啥用的垃圾,这些噪声大大降低了信噪比 S/N,所以它们的性能天花板 C 就低。

但好处就是当程序员的爽了,你不用管内存,不用管类型转换的细节,你只需要关心业务逻辑,人工编码开销大大降低了。


所以这是信息论约束下编程语言的第一个矛盾,信道容量和编码质量之间的权衡,甘蔗没有两头甜。

既要放摇滚乐又要听清楚房间里别人的悄悄话,你这是在祖师爷坟头蹦迪,你知道吗?

祖师爷棺材板都压不住了。


第二个矛盾来自于信息熵,祖师爷又说了:

\begin{aligned}  H(X) = -\sum_{i=1}^{n} P(x_i) \log_2 P(x_i)  \end{aligned}

一个事件的概率越低啊,它发生时所带来的信息量就越大。

一个系统里可能发生的状态越多、越混乱,它的熵就越高。

一个程序从源代码到最终在CPU上正确运行,它所需要消除的总熵是恒定的,不妨称之为编程熵 \mathcal{H}

 \mathcal{H}_{编程熵} = \mathcal{H}_{逻辑熵} + \mathcal{H}_{实现熵} + \cdots

逻辑熵是业务固有的,这部分熵是任何语言都必须表达的,不用管它。

主要问题在于实现熵,实现熵表达了如下不确定性:

  1. 类型熵:变量 x 到底是个整数、字符串还是一个动态对象?
  2. 内存熵:变量 x 存在哪?栈上还是堆上?它占多大地方?什么时候申请?什么时候释放?
  3. 派发熵:object.method() 这个调用是虚调用还是内联调用?
  4. 检查熵:访问数组的第 i 个元素,i 会不会越界?对象会不会 NullPointerException
  5. .......

如果想要消除这些熵,就要把所有这些不确定的问题,一个个变成确定的答案。

那么不同语言的分歧,就在于由谁来消除这些熵,以及在什么时候消除。


第一种哲学就是尽可能在程序运行之前,把所有能确定的事情全部确定下来。

根据条件熵公式,我们希望最终程序的执行状态 Y 是完全确定的,那么在给定源代码 X 的情况下,Y 的不确定性 H(Y|X) 应该为0。

所以静态语言就是通过增加 X 的信息量,让编译器在编译阶段证明:

 H(Y|X_{\mathrm{static}}) = 0

那程序员在写代码的时候就必须提供海量的额外信息用来帮助编译器消除实现熵。

你像 Rust 这种所有权系统下的借用检查器更是烦个没玩,你写的每一行代码都要向编译器提供一个内存此刻归谁管、谁能读、谁能写的数学证明。

如果编译器证明不了,借用检查失败,它直接拒绝生成可执行文件。

程序员累成了狗,你现在不仅要写业务逻辑,还要写大量的代码来喂饱编译器,向它证明你的程序是确定和安全的。

换句话说你抽大象抽的越多,相同信道容量 C 下要写的代码反而越来越多了。


第二种就是先爽了再说,天塌不下来,塌下来一起完蛋算了。

那就别管实现熵了,全甩给运行时虚拟机或者解释器。

你是爽了,但信息熵不会凭空消失,这些你没消除的熵,都成了运行时的信息债。

在动态语言里,源代码 X_{\mathrm{dynamic}} 包含的信息量很少,不足以在编译时消除所有不确定性

那么根据条件熵公式:

 H(Y|X_{\mathrm{dynamic}}) \gg 0

这个远大于0的条件熵就必须由 Runtime 每时每刻都通过消耗CPU时间和内存,在程序执行的每一步持续地进行消除。


所以这是信息论约束下编程语言的第二个矛盾,混乱度不可消灭,只能转移。

一个大一统的语言,既要动态语言的开发便利,允许程序员提供低信息量的代码,又要静态语言的运行性能,让运行时熵为零。

你咋不去造神谕机呢。


PL 人都知道,编程语言设计是舍与得的艺术,你不可能同时很简单,跑得快,还非常可靠。

  1. 跑得快 + 很安全,那就是 Rust,那就不可能好写,学习曲线极其陡峭。
  2. 很简单 + 很安全,那就是 Python,那就不可能极致性能,运行时在某些领域就是天堑。
  3. 很简单 + 跑得快,那就是 C,那就不可能安全,全靠人。

所以一个语言想要打所有领域,那就只能所有领域都写起来十分的垃圾。

这里又要祖师爷出马了,祖师爷怎么把所有的东西都想到了,他真的,我哭死。

根据香农信源编码定理,使用大一统语言解决任何一个领域内的问题时,是在用一个为平均信源设计的编码方案来编码一个特化信源的有效信息。

这种编码错配带来的效率损失服从 KL散度公式:

 D_{KL}(P || Q) = \sum_{i} P(i) \log_2 \frac{P(i)}{Q(i)}

当我们用基于 Q 的编码方案来编码信源 P 时,其实际的平均码长 L 满足:

 L = H(P) + D_{KL}(P || Q)

那么使用 L_{大饭桶} 来写比如数据库代码时的平均码长 L_{数据库} 就是:

 L_{数据库} = H(S_{数据库}) + D_{KL}(P_{数据库} || P_{大饭桶})

KL散度 D_{KL}(P || Q) \geqslant 0 当且仅当 P=Q 时等号成立。

因为大一统语言要兼顾所有领域,它的概率分布 P_{大饭桶} 不可能等于任何一个专有领域的概率分布。

因此  P_{数据库} \neq P_{大饭桶} \implies D_{KL}(P_{数据库} || P_{大饭桶}) > 0

那么用大一统语言写数据库代码的平均长度 L_{数据库} 必然严格大于理论最优值 H(S_{\rm{DSL}})

然后你把所有专有领域的 KL 散度加起来就是这个 L_{大饭桶} 的垃圾程度了,应该没有比这种语言更加浪费饭菜、浪费能源、浪费生命的了。