编程语言
关于语言这件事,程序员圈子里面就没有停止过争吵。 • 26 October 2016
本人其实没有足够的资格谈论这个问题,其实真正用过的语言也就C/C++,PHP,JS。其他的很多语言也只停留在知道的阶段,当然用过的语言也不精通,只是想就这个一直堆积在自己心里的问题做一个总结,也算是给不枉这几年跟大家的讨论。以下观点仅仅是个人简介由于水平有限肯定有很多疏漏,望见谅,欢迎交流,也会不记名提到一些同学,没有任何恶意仅仅说明现象。
语言不是程序的全部
先从一个简单的事情说起。这一部分内容在计算机专业的同学看来应该不是问题,大家应该不会这样想问题,但是其他人往往不这么看。
记得三年前,在刚来到大学,认识计算机,认识编程这件事情的时候,就觉得会一门语言是一种能力的体现,因为很明显,语言的使用场景大都是不同的,你多会一门语言就意味着你能在一个新的领域做一些新的事情。因为当时还是抱着“我会什么才能做什么”心态的。(我认为程序员对于自己程序设计技术最终能够完成的产品的能力有三种心态,一是“我会什么才能做什么”,二是“我能学会什么我就能做什么”,三是“我认识可能会什么的人,我就能做什么”,这种心态的发展也体现了程序员的“浪”和成长)
学着学着发现了编程语言的统一性,原来不同语言只是语法不同啊(后面会反驳这种观点),于是自己很少再去接触新的语言了,只是把不同的语言当成工具在使用。但是某个假期大家一起出去玩,有个高中同学问我“现在会多少编程语言”的时候我就有点愣了,想了想他应该不是讽刺我吧,可能不太了解情况吧。
编程语言虽然在每个层面执行着不同的任务,但是程序设计思想其实是大于语言的。原因我就不多解释了,大家应该也都清楚。
不同语言不仅是语法不同
有些深入研究某个领域的老师,或者其他专业接触编程语言较少的同学可能持有这种观点,相信很多以工具心态使用编程语言而且很少开发大型工程的人应该也有这种体会。如果把条件、循环的结构换一下、API的名称了解过后两种语言的区别就不剩什么了。这样想也没有什么问题,因为这种想法写出的指令式的程序也能正常运行,往往还能降低学习成本,完全不用在意理解和实现的细节。
通过语法可以把语言大概分成几类,标签式(VB,Matlab,…),缩进式(python)和{}式,个人比较喜欢{}这种c-like的语法,灵活清楚。
但是我认为,不同的语言还是有很大不同的,其实这篇文章之后可能都要讨论这种不同,每种语言的作者在发明这种语言的时候的思想就会潜移默化的影响你的程序设计行为,尤其是在构建大型工程的时候,往往每种语言为了实现大工程的“优雅”都有自己的一套写法,他们的运行机制也往往不同,而理解这些应该是深入认识一门语言的基础。
如何认识变量,如何处理内存,需不需要虚拟机,采取怎样的回收机制,这样实现对象,怎样实现异步其实都是语言需要考虑的不同点。
语言的分类
如果把语言分为两类,你认为最重要的不同点应该选哪个?应该不会有太多人按照标签式、缩进式来分吧?如果回答过程、对象、函数式那么我觉得也没啥问题,这也可以算是语言能够提供的一大区别了。但是本文就不讨论函数式相关的事情了,因为真的不懂。
个人认为语言可以按照对变量本质的认识上,这样可以吧语言分为两类,C/C++一类,JAVA,JS,Python,等等其他语言一类。C/C++认为变量指的是一块内存空间,修改变量是修改内存确定地址中的内容所以在这种语言中 “=” 应该解释为复写内存之意,然而JAVA为代表的这类语言认为变量是一个符号,它跟数据(或者说常量)是分离的只是指向引用的关系,而数据是不会动的也是不能改的。
相信使用C语言的人如果不了解JS语言的机制,得知JS中字符串是常量应该也觉得很惊讶吧,可能这种时候都不知道应该怎样处理JS中的字符串了,好想a[0]='A'
赋值啊怎么办😥?
从C转到第二类语言的人也会在学习的时候也会经常问一个问题,python(或者JS)语言中的函数调用到底是传值、传地址还是传引用呢?往往老师会告诉“传引用”,虽然这么说不确切但是我还真听过有这样说的。其实传引用也不能说没有道理,只是与C++表现不同罢了,如果你恰巧用这两种语言,那么你应该能看懂以下例子中函数对外层变量的作用是不同的。
// C++
void foo(int &a) {
a = 1;
}
// js
function foo(a) {
a = 1;
}
其实传进来的都是地址,或者说引用吧,(传引用也是另一个变量了,但是从JS的角度来看C++这种行为像是传名,当然这么说也不对),并不是怎样传递导致的作用不同,而是内部赋值语句的运行操作不同了(一个复写,一个引用迁移)。
个人认为这个不同是语言之间的根本区别,确定了这个不同之后,基本之后的是否需要使用虚拟机,是否需要回收内存,等等都能够大概确定了,之后可能就是在回收内存到底是jc还是指针计数之类的问题上了。
另外说一句像C语言这样第一类语言,你编的程序就是最后运行的程序了,虽然你可能引用了很多库,但不影响这个特点,换句话说,如果所有的库都是你写的,你的程序只要编译就可以运行了,什么都不需要依赖。但是其他语言就没有这么单纯了,往往需要一个程序辅助运行来解释你程序的运行帮助你的程序来解决内存等问题,甚至需要一个虚拟机(大家往往这么做)。
作用域与闭包
C语言之中根本没有提过闭包这个概念,因为C语言设计机制简单,作用域判断也尽量的简化,到最后只留下了静态链,把动态链去掉了(这是编译里的概念)。于是一个函数,一个过程就不能保有自己的变量(注意这里指的是函数闭包变量,是与函数同生命周期的,而不是与函数的执行同生命周期的参数变量和临时变量),现在的动态语言往往实现一个比C语言稍稍复杂,但是也不能算是完整的动态链的东西,用这个来解决新的闭包问题。
这从根源上来说是因为动态语言想把函数(或者说类)作为他的第一成员导致的,如果函数是第一成员,他就不像其他数据成员那样单纯,因为C中只有数据成员,不存在两个数据成员的相对生命周期问题。在动态语言中这里还存在一个弱引用的问题,往往给人带来很多困扰,总的来说设计有利有弊,这才是不同语言的魅力,在了解了这种机制之后其实也好处理和转化,用起来到是方便。
语言暗示
语言的暗示比语言更加重要!我一个很好的朋友跟我说过这么一个例子(来自《Java程序员 上班那点儿事》 - 3.2.5 也不要过于迷信C语言)如下,解释为什么java运行比C++快。
int getXXX(int x, int y) { // java
return x+y;
}
int getXXX(int x, int y) { // C++
for(int i = 0; i < 10000000; i++){
string ss = "I am so slow";
}
return x+y;
}
在大家看来,这也许就是一个笑话。那么我再举一个例子,大三的时候我们学编译,由于能力有限只能选择更好实现的JS来做大作业(一个阉割版pascal编译器),很多同学选择使用C++(推荐)来实现这个编译器,我是很佩服用C++写编译器这些人的,因为我觉得这东西C++写太难了。
然而这里我想说说效率问题,写过leetcode的同学应该知道js同样算法平均是比c慢100倍的。而在这个编译器任务中我需要构造一个上千状态的自动机,这个状态机是自动构造的,其中每个状态都需要各种调整和比较,相同的指导思想,我用js写的这个状态机是可以在5s之内构造的。相比之下C++写的状态机需要1分钟左右才能构造完成。
相同的指导思想,然而js快这么多是为什么呢,因为这之中存在大量赋值操作,类对象的大量转移、创建,在js之中使用的相同表达式在每个状态中都是共享的,然而在C++中我想大家也不能做成这样,我大概猜一下,每个状态类对象下的每个表达式定义为了表达式对象数组,每个表达式其实都是源表达式的拷贝,这样C++程序就花了大量时间在新建对象和拷贝对象上,但是第二类语言就很自然的绕开了这些操作,反而得到了更高的效率。
考虑这件事情的根源其实就是C++默认“=”深拷贝,而js是赋引用了。当然,如果C++全都合理的使用了引用和Hash(toJSON(self))方法进行比较和赋值,一定能够达到比js快100倍的效果的。我不是说所有情况js都比C++暗示的好,但是起码在这件事情上,一些C++程序员就被在不知情得情况下绑架起来写了10000000句的string ss = "I am so slow";
吧。
语言对你的影响是潜移默化的,跳出语言想架构,跳入语言做实现可能能够减少大型失误,但是细节上还是会有根深蒂固的影响。
类与对象
这里举一个例子吧,也是我最不好说清楚的例子了。前阵子看到一篇文章《function/bind的救赎(上)》(http://blog.csdn.net/myan/article/details/5928531)提到这个“怎样实现对象机制”的问题,里面解释了我一直以来有的疑惑,现在大家采用的面向类编程的机制可能完全违背了“对象理论”的设计初衷。从这个角度其实可以把Smalltalk,OC分为一类,其他对象语言分为一类了,目前来看面向对象设计中Java的影响还是非常大的,大型工程中能够活用之后动态脚本语言特性的还是太少了。
关于异步
异步执行这件事情其实也是我接触JS之后才有这么大的体会的,当然之前为了能够更好地利用计算机的运算资源也是做了很多的尝试的,比如进程、线程、管程这个发展。到JS这里直接让程序之中的每一个耗时操作都异步执行了,这样做确实对性能有很多提升,尤其是并发的情况,不用系统处理线程,切换线程了,也是对系统的一种释放。
但是这种修改导致JS的写法都和正常的语言不同了,后来大家就开始研究如何解决这种诡异的Callback写法了,甚至搬出Promise来解决问题,这两天node发布7,也把async,await支持上了,版本迭代的过程中,之前的库就不能支持,在本语言内部出现了一套各种feature的编译器(babel)打得火热。
总的来说出现问题想解决办法还是一件好事,但是我觉得这事情做的越来越不优雅了。甚至我觉得完全可以从出发点想办法。但是想想这种事情往往是这样的,比如被称为良好分层设计的计网,不也由于历史的原因,有各种不正常的依赖、协议层出不穷么。
更多
关于语言这件事,程序员圈子里面就没有停止过争吵。这篇文字的强行堆砌可能不会有太多人看完再后面吵架了吧(还是希望大家能够多交流的)。想想自己也没有什么资格谈论这个问题,算是对自己的思想有个解放,不在去想这个事情了,到这里总算是把大三以来关于语言的思考都差不多记下来了吧,怎么说也是用过世界最好语言和发展最快的语言的人啊。