首页 / 软件开发 / 数据结构与算法 / 如何设计一门编程语言(十一) 删减语言的功能
如何设计一门编程语言(十一) 删减语言的功能2014-08-23大家看到这个标题肯定会欢呼雀跃了,以为功能少的语言就容易学。其实完全不是这样的。功能少的语言如果还适用范围广,那所有的概念必定是正交的,最后就会变得跟数学一样。数学的概念很正交吧,正交的东西都特别抽象,一点都不直观的。不信?出门转左看Haskell,还有抽象代数。因此删减语言的功能是需要高超的技巧的,这跟大家想的,还有跟go那帮人想的,可以断定完全不一样。首先,我们要知道到底为什么需要删减功能。在这里我们首先要达成一个共识——人都是很贱的。一方面在发表言论的时候光面堂皇的表示,要以需求变更和可维护性位中心;另一方面自己写代码的时候又总是不惜“后来的维护者所支付的代价代价”进行偷懒。有些时候,人就是被语言惯坏的,所以需要对功能进行删减的同时,又不降低语言的表达能力,从而让做不好的事情变得更难(完全不让别人做不好的事情是不可能的),这样大家才会倾向于写出结构好的程序。于是,语法糖到底是不是需要被删减的对象呢?显然不是。一个好的语言,采用的概念是正交的。尽管正交的概念可以在拼接处我们需要的概念的时候保持可维护性和解耦,但是往往这么做起来却不是那么舒服的,所以需要语法糖。那如果不是语法糖,到底需要删减什么呢?这一集我们就来讨论面向对象的语言的事情,看看有什么是可以去掉的。在面向对象刚刚流行起来的时候,大家就在讨论什么搭积木编程啊、is-a、has-a这些概念啊、面向接口编程啊、为什么硬件的互相插就这么容易软件就不行呢,然后就开始搞什么COM啊、SOA啊这些的确让插变得更容易,但是部署起来又很麻烦的东西。到底是什么原因造成OO没有想象中那么好用呢?之所以会想起这个问题,其实是因为最近在我们研究院的工位上出现了一个相机的三脚架,这个太三脚架用来固定一个手机干点邪恶的事情,于是大家就围绕这个事情展开了讨论——譬如说为什么手机和三脚架是正交的,中间只要一个前凸后凹的用来插的小铁块就可以搞定,而软件就不行呢?于是我就在想,这不就是跟所谓的面向接口编程一样,只要你全部东西都用接口,那软件组合起来就很简单了吗。这样就算刚好对不上,只要写个adaptor,就可以搞定了。其实这种做法我们现在还是很常见的。举个例子,有些时候我们需要Visual C++ 2013这款全球最碉堡的C++ IDE来开发世界上最好的复杂的软件,不过自带的那个cl.exe实在是算不上最好的。那怎么办,为了用一款更好的编译器,放弃这个IDE吗?显然不是。正确的解决方法是,买intel的icc,然后换掉cl.exe,然后一切照旧。其实那个面向接口编程就有点这个意思。有些时候一个系统大部分是你所需要的,别人又不能满足,但是刚好这个系统的一个重要部分你手上又有更好的零件可以代替。那你是选择更好的零件,还是选择大部分你需要的周边工具呢?为什么就非得二选一呢?如果大家都是面向接口编程,那你只需要跟cl.exe换成icc一样,写个adaptor就可以上了。好了,那接口是什么?其实这并没有什么深奥的理解,接口指的就是java和C#里面的那个interface,是很直白的。不知道为什么后来传着传着这条建议就跟一些封装偶合在一起,然后各种非面向对象语言就把自己的某些部分曲解为interface,成功地把“面向接口编程”变成了一句废话。不过在说interface之前,有一个更简单但是可以类比的例子,就是函数和lambda expression了。如果一个语言同时存在函数和lambda expression,那么其实有一个是多余的——也就是函数了。一个函数总是可以被定义为初始化的时候给了一个lambda expression的只读变量。这里并不存在什么性能问题,因为这种典型的写法,编译器往往可以识别出来,最终把它优化成一个函数。当我们把一个函数名字当成表达式用,获得一个函数指针的时候,其实这个类型跟lambda expression并没有任何区别。一个函数就只有这两种用法,因此实际上把函数去掉,留下lambda expression,整个语言根本没有发生变化。于是函数在这种情况下就属于可以删减的功能。那class和interface呢?跟上面的讨论类似,我主张class也是属于可以删减的功能之一,而且删减了的话,程序员会因为人类的本性而写出更好的代码。把class删掉其实并没有什么区别,我能想到的唯一的区别也就是class本身从此再也不是一个类型,而是一个函数了。这有关系吗?完全没有,你用interface就行了。class和interface的典型区别就是,interface所有的函数都是virtual的,而且没有局部变量。class并不是所有的函数都是virtual的——java的函数默认virtual但是可以改,C++和C#则默认不virtual但是可以改。就算你把所有的class的函数都改成virtual,那你也会因此留下一些状态变量。这有什么问题呢?假设C++编译器是一个接口,而Visual C++和周边的工具则是依赖于这个class所创造出来的东西。如果你想把cl.exe替换成icc,实际上只要new一个新的icc就可以了。而如果C++编译器是一个class的话,你就不能替换了——就算class所有的函数都是virtual的,你也不可能给出一个规格相同而实现不同的icc——因为你已经被class所声明的构造函数、析构函数以及写好的一些状态变量(成员变量)所绑架了!那我们可以想到的一个迫使大家都写出倾向于比以前更可以组合的程序,要怎么改造语言才可以呢?其实很简单,只需要不把class的名字看成一个类型,而把他看成一个函数就可以了。class本身有多个构造函数,其实也就是这个意思。这样的话,所有原本要用到class的东西,我们就会去定义一个接口了。而且这个接口往往会是最小化的,因为完全没有必要去声明一些用不到的函数。于是跟去掉函数而留下匿名函数(也就是lambda expression)类似,我们也可以去掉class而留下匿名class的。Java有匿名class,所以我们完全不会感到这个概念有多么的陌生。于是我们可以来检查一下,这样会不会让我们丧失什么表达方法。