这是一篇关于编程语言的小作品。男怕入错行,请问我应该学什么编程语言?
答案:如果你问这个问题,说明你多半不是学计算机出身的。我的建议是先学计算机(指的是本科课程)。
在讲编程语言之前,先讲讲自然语言。自然语言可以用来传递信息,信息的作用是消除不确定性,而消除不确定性可以提高未来收益的期望值,所以信息的传递是人类最重要的活动之一。为了传输信息,我们需要使用一定的编码,而自然语言就是人类最常用的一种对信息进行编码的方式(用字、词、句来表示信息)。
自然语言有以下几个特点:
一定的编码效率、冗余度和容错性对于信息的可靠传输非常重要。自然语言的编码效率是比较高的,比如下面这句话:
“你没吃饭吗?”
以“你”为例,虽然“你”这个字并不代表某个特定的人,但在双方对话的过程中,因为互相都认识(共同拥有对方身份这一信息),所以需要传递的信息只有一个汉字,即“你”或者“我”,缩短了信息编码后的长度,降低了传输的成本。
而自然语言的冗余度和容错性也都还可以,假如我们让另一个人帮我们问这个问题,那么他可能会像下面这样问:
但我们听到之后都会不假思索地理解为同一个意思。这说明自然语言所承载的信息,由于冗余度和容错性都比较高,对于传输过程中发生的微小变化并不敏感,降低了解码的成本。
总之,对信息进行编码,不管是自然语言还是CDMA,关键都是权衡传输和编解码的成本。
缩短编码长度可以帮助我们降低传输及存储信息的成本。在纸张大量生产之前,文字被记载在竹简上,而刻字的成本非常高,所以人们就发明了一种非常精简的编码,也就是文言文。文言文的优点是存储和传输的成本都很低。但是大家平时交流都用白话文,没有人用文言文,这是因为文言文虽然编码效率非常高,但是冗余度和容错性都不好,解码成本很高。
比如我们要复制一卷竹简的内容,即把每个字重新刻一遍,那么文言文可以降低复制的成本。但一个人如果想从文言文中获取信息,他的传输成本虽然很低,但解码成本会非常高。之前讲过信息的作用是消除不确定性,而在学校学过文言文的同学都知道,文言文最大的毛病就是不确定性太高,往往需要借助很多上下文、历史背景、文体风格等等,对具体字词的意思进行推敲,才能最终搞清楚作者的意思。这就是为什么大家在日常生活中不使用文言文,因为相比所传输信息的价值,编解码的代价实在是太高了,得不偿失。
有没有什么办法既能缩短编码长度,又保证冗余度和容错性(降低解码成本)呢?有的,那就是双方事先规定一整套编解码的规则,然后把这套规则练熟。比如普通话就是一种由九年义务制教育强制要求所有中国人都得学习的编码规则,所以全中国人民都可以使用普通话相互交流。如果没有事先学习普通话,相互沟通协作就很困难,所以历史上中国总是分裂成一个个小国家,而秦始皇统一中国后最重要的工作就是统一文字。
因为古代没有电视和收音机,所以语言很难统一,但是文字可以。
通信双方事先规定了一整套编解码的规则,这个规则是双方共享的已知信息,可称为“互信息”。
从信息论的角度来看,说同一种语言的人,相互共享的信息非常多(“互信息”非常高),因此需要进行传输以消除不确定性的信息量就比较少,沟通效率也就更高。随着现代物理技术的发展,信息的传输和存储成本越来越低,真正的成本反而落到了人类对这些信息的编解码过程上。在今天,想要获取世界任何一个角落的音视频资料都不困难,真正的困难反而是获得这些资料之后,由于语言不通无法解读。按照优胜劣汰的规律,世界上自然语言的数量会越来越少(降低日常生活交流成本),而各种术语、“行话”会越来越多(降低特定领域内的交流成本),这和人类近几千年的历史是吻合的。
换句话说,普通话仅仅是应对日常生活的最优编码,不是所有情景下的最优编码。比如在科研领域,数学概念的传播就需要借助数学符号,以及大量数学相关的英语词汇。像“先验概率”和“后验概率”这两个概念,在英文中就称为prior和posterior,在双方事先规定好编解码规则(都是科学家,都熟悉概率统计)的情况下,英语显然比汉语要简洁得多。当然,英语在科学领域能拥有这样的表达能力,主要是因为现代科学发源于英语国家。这也就解释了为什么想当科学家英语一定要好。
编程语言的作用跟自然语言是一样的,也是对信息的一种编码,设计目的也是为了降低传输、存储和编解码的总成本。比如一个人想让计算机帮他输出Hello world,他会用编程语言将这条信息(“请输出Hello world”)编码为计算机可以理解的形式,然后计算机就可以将其解码并执行。我们可以从信息传输、存储、编解码成本的角度,对编程语言进行定量分析。
刚才讲过,随着现代物理技术的发展,信息的传输和存储成本总是越来越低的,因此我们忽略这两个角度,只考虑编解码成本,即从代码到概念、从概念到代码的转换成本。
以两种语言的Hello world为例:
#include <stdio.h>
int main(int argc, char* argv[]){
int i;
for(i=0;i<10;i++){
printf("Hello World!");
}
return 0;
}
for i in range(10):
print('Hello World!')
显然,python代码的编码长度更短,而且传达的信息并不比C代码少。
这是因为,在C代码中,
#include <stdio.h>
即便不写,编译器通常也会包含,所以信息量为0int main(){}
即便省略,我们也能搞懂作者的意图,所以信息量为0argc
和argv
大多数程序都不需要,信息量为0.1bitfor(i=0;i<10;i++)
真正重要的只有数字10而已,信息量为2bitreturn 0;
即便不写,helloworld的任务也能正常完成,所以信息量为0正是因为我们已经预先知道了很多信息,而且这些信息跟我们想表达的意思完全无关,所以才会觉得C语言非常笨重、表达效率不高。要知道C语言最早是设计用来编写操作系统内核的,它假设程序员会用它做几乎所有可以用汇编实现的事情、构建操作系统的所有必要组成部分。对于这些工作,int main()
表示的是一段可执行代码的入口;argc
和argv
表示该入口期望的输入参数类型,如果搞错了可能会导致栈不平衡,系统不久就会崩溃或者被黑客攻破。因此这个信息就不是可有可无,而是非常重要的了(尤其是对编译器来说)。实际上,C语言已经是进行操作系统软件开发工作时,程序员编解码成本最低的语言。
因此要比较编程语言的优劣,我们必须先规定一个使用情景,在这个使用情景下考察信息的编解码成本。很快你就会发现,其实每个使用情景都已经用上了编解码成本最低的语言。
比如“臭名昭著”的Java,它的helloworld是这么写的:
// 首先你要创建工作目录,可能还有maven等一系列劳什子
// 这里才是真正的代码
public class HelloWorld {
public static void main(String[] args){
for(int i=0;i<10;i++){
System.out.println("Hello World!");
}
}
}
// 然后你要编译
Java编码长度这么长,请问它降低了哪门子编解码成本?其实Java提供的这种冗余,跟自然语言的优点是一样的。
java代码很冗长,而这恰好使得阅读它很轻松。比如System.out.println()
,虽然长度比print()
长,但是放在一大段代码中,前者更容易被发现、被视觉分类,加上形状固定,实际上阅读效率更高。这就类似于白话文虽然比文言文啰嗦,但读起来比文言文轻松。
如果你有一个好的IDE,再啰嗦的代码都不是问题。这就如同有了现代拼音输入法之后,大家宁可用比较啰嗦的拼音,也绝不会用笔画或者五笔输入。因为拼音输入的互信息基础就是普通话的发音,而普通话又恰好是我们最熟练的编码方式,因此编码所需的脑力非常少。有了好的IDE之后,java程序员输入代码的效率跟其他语言就没有区别了。
在java中,所有的东西都是对象,比如程序本身,以及main的输入参数(字符串数组)。而面向对象的各种设计模式尽管啰嗦,但它们在程序员中无人不知(相当于预先规定了一整套规则),因此用面向对象的方法编写的程序,在阅读的时候由于互信息很高,加上冗余度也很高,因此一个程序员书写的程序,另一个程序员很容易读懂。在很多动态语言中常见的闭包等特性,容易导致写出虽然能工作,但其他程序员未必能轻易看懂的代码。
也就是说java的语法虽然冗长,但它降低了团队开发情景下每个程序员对其他程序员所撰写代码的编解码成本。这就是为什么大公司里的程序员几乎清一色都是Java开发,项目清一色都是Java项目。Java就像普通话,不管是作家还是文盲都能使用它,互相之间也都能听得懂,这是它的优点。如果一个程序员过劳死了,马上让另一个程序员顶班,问题就解决了,这是它的优点。
总而言之,在大公司团队开发情境下,Java效率最高。
Java的成功还有另一个更深层次的原因:像Java和.NET这样的框架提供了绝大多数日常软件开发工作所需的一切,因此很多软件工程师可以在完全不关心计算机底层原理的情况下,进行软件的开发和维护。这就降低了软件工程师的人力成本。
从上面的例子换一个角度想,如果程序员和计算机之间的互信息足够高,程序员和程序员之间的互信息也足够高,那么在编解码成本不变的情况下,程序代码是不是就可以缩短呢?没错。考虑极端的例子:如果把软件开发工作的规模缩减到一个人,并且允许这个人设定计算机端的编解码规则,那么程序代码可以缩短到难以置信的地步。
以Hello World为例,如果用Lisp,代码可能会长这样:
(defun times ........)
(times 10 '(prn "hello world"))
如果用JavaScript,我会这样写:
var times = (x,f)=>{while(x--)f();}
times(10,()=>console.log('hello world'))
lisp/python/javascript等语言最大的优势,是它们可以在完全不使用面向对象编程的情况下,实现面向对象所无法实现的功能。C++/Java帮你定义好了你能做的所有事情;而在这些语言中,你可以方便地定义你能做的事情(以及做事情的方式),这些事情在C++/Java中往往需要大量使用模板/容器,但在这些语言中可以随意拼接。说到底,因为Java是静态语言,不提供除Class之外把数据和代码绑定到一起的其他方法。在Lisp、Py和JS中,闭包可以说是最重要的语言特性,而这个特性在Java中必须通过动态地创建Class替代。
而C++/Java也在越来越多地借鉴动态语言中早已拥有的很多特性,比如lambda表达式等等。
JavaScript在服务器端的成功也证明了,写出优美而强大的程序并不需要Private Friend Inherit Class,而只需要List Set 和Function,这三者在JavaScript里是嵌入的数据结构,用起来比class方便不知道多少倍。
总而言之,动态语言允许我们任意设定规则,缩短编码长度,提高开发效率。
但是这种搞法也有对应的几个缺点:
所以这种搞法往往被用在需要一个人解决一大堆问题的场景下(像创业团队就喜欢用各种动态语言)。为了应对这些缺点,微软就搞了一个TypeScript,专门给JavaScript加上语法糖、类型验证、自动补全等等,最后TypeScript开发起来就会跟C#差不多,团队开发生产率就上去了。
除此之外,对于号称“最强大的语言” Lisp我有一些看法。为什么Lisp流行不起来?起初我觉得是历史的原因,后来我知道就是因为它表现力太强,导致同一种事情可以有无数种做法。当同一种事情有无数种做法的时候,它们之间就很难兼容,因此也就很难评价哪种方法好。而且一旦发生项目换人,Lisp就很可能导致灾难。
另一方面,当同一种事情有无数种做法的时候,平庸的程序员是不知道应该选择哪一种的,最后往往就选择了不那么优美的做法。而Java则凡事都可以直接抄书,显得方便不少。
你是喜欢吃大锅饭,还是骑独轮车?你是希望让别人读你的代码开心,还是你自己写得开心?对这些问题的回答,左右着我们对编程语言的选择,也左右着每个人的人生轨迹。就写到这里吧。