真的是太难了,无论我如何设计,总有很多的问题。所谓的语义问题还只是很小的部分,最大的问题,就是如果填平高层抽象和底层实现这条巨大的鸿沟。
现在的所有语言都在语义上有问题,比如我们到处都能看到类似a = 1这种语句,在我们的习惯上的认识上,这是赋值。但其实这不是赋值啊,这只是在说明a等于1。很多人崇拜数学,那么就从数学上说,最基本的代数,其实没有赋值这种东西,a=1,只是说明a的值是1,也可以认为是在说明a这个符号所代表的内容与数字1是等价的。比如方程式,a+2b=1,只是在描述这样的一种情况:a+2b的结果,和数字1是等价的,a+2b的结果是数字1。而再往下面加几句:a=0.5 b=0.25,就是在说,a等价于0.5,b等价于0.25,0.5 + 2 × 0.25 = 1。=只是说明左边的东西和右边的东西是相等的,等价的。
问题就来了,目前大部分的语言,至少我们都非常习惯的语言,=都用来表示赋值,=的语义变成了将左边的东西的内容,变成右边的东西的内容。a=1,变成了将a改成1。目前还不是很混乱。但是,出现这这样一个语句:a = a + 1,这是混乱的。a就算是某个值相等,只会是:a = a或者a = a自己的值。而a = a + 1,就是在说:a这个东西代表的值,比a自己的值要大1点,逻辑上是错误的。为什么这么说呢,虽然这些语言的=不是表示相等,而是赋值,但我们仍然会被这个所影响,会混乱。比如一个著名的例子:
if (a = 1)
{
...
}
我相信不少人都这么写过,因为我们从小到大,在认知上=这个符号就是表示相等,而我们又习惯了那些编程语言,所以我们有时会在那些语言中,错误的使用了数学上的语义。就是上面的例子,我们的意图是:如果a的值是1,那么就...,但编译器看到的是:先让a的值变成1,然后如果a,那么就...。在早期,这种情况肯定出现的不少,不然也不会流传出这样的应对方式:
if (1 == a)
{
...
}
因为在这些语言中,==才表示是否相等。问题不单单是我们会有时错误的使用数学上的语义,也会是漏掉了一个=符号,if (a == 1),就会变成if (a = 1),这是2个方面的原因。这这个颠倒被判断对象位置的方式,同时避免了这两种情况的发生。但这样对吗?我虽然这是这样做的,但我在内心深处,是不习惯的,这样是不自然的。而且,如果两个都不是常量,比如if (a == b),依然无法避免错误的写成 if (a = b)。
但问题是否就是如此呢?不是,问题的根源不在于不应该将=作为赋值,而是=为什么会返回一个值。为什么a = 1,会返回一个值,在大多数语言中,一个赋值语句会返回被赋值者本身,也就是a = 1会在将a的值变成1后返回a。但这在逻辑上根本就说不通,因为这是一个赋值动作,是一个命令,不是一个函数。不应该返回一个东西,这才是正确的实现。如果当初是这么做的,那么永远都不会有:if (1 == a)这种对策。而是编译器直接就告诉你:”错误:if (a = 1),a = 1是一个赋值语句,没有返回值,if语句的条件必须是一个布尔表达式。“
大家可以思考一下,如果当初是这么做的,是不是现在我们根本不会看到if (1 == a)这种东西,是不是永远都不会有漏写一个=符号而造成语法有效甚至语义有效,而实现不正确的情况。之所以赋值语句会返回值,会在赋值后返回那个被赋值者的值,还有一个原因是支持这样的语法:a = b = c = 1,但这不会让代码简洁,而只会造成问题。这是一个历史遗留问题,当初的计算机很弱,开发者需要竭力的节省资源,内存占用不能高,效率不能低,什么东西能省就省,能小就小,能合并就合并,复杂指令集这种东西,也就是因为这样才出现的。这种用法在逻辑上是错误的,赋值语句不应该返回值,但这时却反而和数学的语义差不多了,a = b = c= 1,可以看成a、b、c、1是相等的,这反而在逻辑上没有问题了。
另一个极端就是纯函数式语言,完全以数学为本。其实我们常用的那些语言,虽然语义上有问题,但语义上的问题只会比数学少而不会多,也远比数学更强大,以数学为本,其实是将能结合计算机的强大语言,变成一个背离计算机的弱小的语言。就比如所谓的副作用,每一个函数对应输入集合a,只能返回对应的输出集合b,纯函数式,连一个获取随机数的函数都做不出,因为那有副作用,最后还只能以非常麻烦非常病态的方式,在回避所谓副作用的同时下实现随机数,但其实也还只能实现靠固定运算的低端随机,访问硬件随机器的函数别想了,因为没有手段回避副作用。我只敢说这么些了,不敢再说了。
语义问题还只是很小的部分,语言真的非常难设计,非常的难。现在看上去有多的数不清的语言,有价值的也只是沧海之一粟,而有价值并在语义上问题少的,少的可怜,有价值、语义问题少,并能有效的填平,至少是填住一些高层抽象和底层实现鸿沟的,没有。
目前可能唯一一个“没问题”的语言就是汇编了,因为汇编的高层抽象就是机器本身,底层实现也是机器本身,没有任何语义问题,几乎是目前唯一一个设计的好的语言。但我们需要的是高级语言,我们要解决的高级语言的问题。从面向过程,到面向模块(我一直认为C其实应该属于面向模块),到面向对象,一直在尝试。
一代又一代的隔离底层细节,一代又一代的提升对高层抽象的支持。但实际上,细节问题依然无法避免,而高层抽象也没支持的多好。鸿沟,依然是非常的巨大。比如C++,不但没有实现不需要关注底层细节问题,而且还多出一个胶合层:类。你需要在底层细节类高层抽象中来回的转,你同时要维护三个领域,甚至要更多,所以Linus说C++恐怖。而Java呢,也没有解决,你还是不能避免底层细节的问题,按理说Java去掉了指针,有GC机制,应该就可以完全不管了啊,但实际上你还是要管,Java不但会内存泄漏,还漏的更彻底,于是你还得在所有的地方,去注意这个问题,去避免这个问题。在Java帮你隔离底层细节的同时,也让你彻底失去了对底层细节的控制,这是一件好事,也会是一件坏事。因为细节问题是最麻烦的,最变态的,再强大的系统,也可能被一个小细节问题所击垮。
代码的实现非常分散,逻辑混乱不清。以类为语言的设计基础,我敢说,是失败的。类这种概念是失败的。其中一个方面,就是你很难将高层事物,很好对应成类,你需要做大量的工作,搞出大量不知道干什么的代码,把这些组合起来,才能把高层事物,对应到代码中。以类为基础的语言,有三个领域,一个是底层实现,一个是类,一个是高层事物,你需要同时在这三个领域搞,非常的累。
另一个方面,就是类本身不合理。比如这样:
class human
{
public:
head& GetHead();
hand& GetLeftHand();
hand& GetRightHand();
troso& GetTorso();
Leg& GetLeg();
...
int GetAge();
void SetAge(int);
private:
head m_head;
hand m_hand[2];
troso m_torso;
leg m_leg[2];
...
};
(当然这只是一个例子,并不代表human类会这么设计,只是用来举例)
人有头有双手有双脚有躯干等等,这个没大问题,但是GetHead是什么意思,给我你的头?SetAge又是什么意思,设置你的年龄?年龄可以被随意的设置?根据消息模型的解释,就是说一个方法代表一个消息,比如某个对象向一个humen对象发出 给我你的头 的消息。但是在这里就有问题了,为什么别人发出 给我你的头 这个消息,就一定要把头给他。为什么别人发出 设置你的年龄 这个消息,你就能改变你的年龄。
我这里说的是,这个类其实是一个错误的设计,不应该这么设计。而是因为要以弄出非常多的类,层层抽象,不能直接弄出一个human类,而是要弄出很多细化的类和一些大类(world类等),通过组合这些类来达到可以弄出human这个概念的效果。
为什么这么说呢,比如一个杂交动物c,它确实是由a和c弄出来的,它确实可以对应继承这个概念,比如:
class a
{
...
private:
head m_head;
troso m_torso;
...
};
class b
{
...
private:
head m_head;
troso m_torso;
};
class c : public a, public b
{
...
};
问题就出来了,a有头有躯干,b也有,概念上没问题,但类有问题。这个c类,它以哪个父类的head为head,以哪个父类的torso为torso。如果都有效那就是怪物了。你就算这样:
class c
{
private:
a m_a;
b m_b;
};
还是一样的,设计的意图,难以通过类来实现。你不能直接弄出一个对应高层事物的类,只能是搞出一大堆的类,通过这些类的行为,来体现出 c是由a和b杂交而来的动物 这个效果。还不是几个类的问题啊,至少都是成千上万的类啊。所以我们需要对应高层事物时,不是直接用类来对应,也不是用大量的类来体现出这个概念,而是用别的办法来表现出这个高层事物,大概的表示这个概念。
也只有在对应计算机本身事物的时候,类才能直接对应了,比如栈、数组、列表等等等等,完美对应,没有任何问题。GUI上的Window、TextBox、EditBox、各种继承关系各种组合关系,都非常的有效,类能直接对应,没有问题。
这说明什么,说明类并没有填平高层事物和底层细节这条鸿沟,只不过是将计算机本身的东西提升了一下表现方式。将 stack* a = stack_new(); stack_push(a, 1); 变成了stack a; a.push(1);
这条鸿沟,是非常巨大的,客观的来说,我们发展的已经非常好了,短短几十年就能将代码发展成这个高度,已经非常不错了,真的是非常不错了。这条鸿沟,就相当于仙女星系和银河星系的距离,那是以光年最单位的。而我们已经走了....大概几公里吧。
为什么新语言层出不穷,就是因为现在的语言实在是满足不了了,我们需要再一次的大发展,需要走出OOP,迎接下一代!
然而这实在是太难了,太难了,无比的艰难。要知道,每一代的发展,至少都是指数级的发展,也就是说,越往后,想要突破就越困难。现在就到了这个非常困难的时刻,因为现在的指数已经很高了,想要突破这个指数,非常的难,无比的艰难。
不知道都多少年前开始,就在说面向组件是下一代,但是呢,这个面向组件在哪里,谁看见了,谁现在使用了,有哪个语言是面向组件的。没有,这个面向组件只是在软件这个层面上,在程序设计语言中,还没有面向组件的。目前还都在面向对象上,不管是静态的动态的解释的编译的桌面的网页的,都是这样。这几十年,仅仅丰富了面向对象本身,没有突破。从汇编到C,才多少年,从Forth、C到第一个OOP语言,才多少年,但是从那一批OOP到现在,多少年了,下一代在哪里。所以说每一代发展的困难度,是指数级的。早期值很小,虽然是指数变化但也搞不到哪里,但到了OOP这代不同了,值已经开始高了,下一代的指数变化已经接近天文数字了。所以,几十年了,我们都还停留在OOP上,没有突破。
因为这真的太难了,就比如我,我设计语言,不管我怎么设计,但感觉毫无意义,还停留在OOP中,不是语义有问题,就是填不了鸿沟。整个人,整个思维都无法突破,到底是为什么。谁,可以解救,可以突破。只有他突破了,我才能知道:哦,原来下一代是这个样子。
我是一个失败者,我没有资格说现在的这些语言,没资格,但我还是想说。现在这么多语言,这么多概念,有几个有用啊!有几个是真正的下一代啊!有几个是语义没问题的!有几个是能,至少是稍微填平鸿沟的!没有!
我们依然还是用着C、C++、Java,...甚至汇编这些老态龙钟的语言。语言,真的不是随便加点语法糖,弄些特性搞些概念就可以的,只弄这些就搞出一个新语言,那只能是过眼云烟。这个语言除了有些语法糖和特性之外,其他都和C/C++/Java一样,那为什么要用这个语言,更何况大部分语法糖和特性,在这些老态龙钟的陈旧语言中也不难实现。
换句话说,如果C++和C不是有着语言本质的差异的话,那么C++有什么用,还用C++干嘛,如果Java不是和C++有着语言本质的差异,那还用Java干嘛。这些老态龙钟的语言,为什么还存在,甚至仍然是我们的主力语言,因为他们是目前有着语言本质上的差别的代表语言,是计算机这几十年来几代语言的代表。
这就是我为什么说,设计一个程序设计语言真的是太难了,因为如果语言本质上没有提升,那这个语言有什么意义呢。
我原来,也是喜欢随意的设计语言,啊,随便弄个新语法的,新概念的,但最后发现,这个有什么用呢,它没有啊。所以现在我最多只敢弄特定方向的语言,比如专门用来嵌入式脚本的,某领域专用的,这些敢想,敢做。因为设计这种语言,我不需要突破,不需要走出OOP,照搬已有的语言就是了。
我常常在想,常常在思考新的的语法设计,来想象下一代语言到底会是什么样子,当然,很难想出来。这太难了,太难了。
一些题外话呢,就是说,现在这些语言本身语义就烂,如果还纠结于语义,那是在折磨自己啊。我看到很多语言,看那些hello的例子,我感觉不出有什么不同,真的,那就是printf("hello world!\n")换了个样子,语言本质没有任何的区别,这些新语言是为什么呢。当然客观来说,新语言虽然都很废,但它们的存在是必要,因为需要大量的尝试,才能渐渐的找到,下一代语言的样子。
这文章说的,其实只是主力语言,也就是通用程序设计语言,是最难设计的,也是编程语言最核心的部分,通用程序设计语言处在什么样的高度,其他语言也就会是什么高度。
文章很乱,希望,在我还活着的时候,能看到那个,走出OOP的,下一代语言!如果是真的,希望那时,我还能用双手写出一个hello。