设计一个程序设计语言真的是太难了

句龙胤 发布于 2014/11/23 08:34
阅读 1K+
收藏 9

真的是太难了,无论我如何设计,总有很多的问题。所谓的语义问题还只是很小的部分,最大的问题,就是如果填平高层抽象和底层实现这条巨大的鸿沟。

现在的所有语言都在语义上有问题,比如我们到处都能看到类似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。

加载中
0
抢小孩糖吃
抢小孩糖吃
见解独到,有深意,从语义上说明语言的问题。我有一些我个人的看法:现在需要解决的问题更倾向算法与语言的跨界问题,如何让一门语言完全脱离计算机基础,变更为只有面向对象、方法实现的语言。软件行业发展巨量的工作都浪费在了A语言有个东西很好,B语言没有这个方法,在用B语言实现,这个时候C语言发现也要具备。人们不停的实现基础类库来解决汇编与使用方法接口的实现。由于ABC语言各有特点,相同的方法或算法分别被ABC语言实现了一遍。
句龙胤
句龙胤
如果脱离计算机基础,那需要建立一个庞大的底层平台。比如建立一个高级模式来,来隔离传统的指令式结构。程序不再以冯诺依曼体系为根基,而是以这个高级模式为根基来设计。不过光这个高级模式就是超量复杂度的东西。
0
句龙胤
句龙胤

引用来自“抢小孩糖吃”的评论

见解独到,有深意,从语义上说明语言的问题。我有一些我个人的看法:现在需要解决的问题更倾向算法与语言的跨界问题,如何让一门语言完全脱离计算机基础,变更为只有面向对象、方法实现的语言。软件行业发展巨量的工作都浪费在了A语言有个东西很好,B语言没有这个方法,在用B语言实现,这个时候C语言发现也要具备。人们不停的实现基础类库来解决汇编与使用方法接口的实现。由于ABC语言各有特点,相同的方法或算法分别被ABC语言实现了一遍。

我的想法就是,如果要实现不依赖计算机底层实现细节的话,可能需要改变整个代码的编写方式。就是不能再以传统的方式编写了,以高级模式为基础,不同的对象,不同的程序,使用“沟通”的方式,而不是调用/返回这种和计算机底层细节密切相关的方式。但这种效果很难做出这样啊,而且使用“沟通”方式,我们需要忘掉以前的一切编程经验,这也是大问题啊。

仅仅是实现让一门语言完全脱离计算机基础,变更为只有面向对象、方法实现的语言。都不容易。因为语言的实现和底层细节密切相关,比如寄存器,各个寄存器的作用约定,内存操作,栈,调用/返回的形式,全部是和计算机紧密结合的。所以如果要完全脱离,那还得需要完全把这些隔离啊,还得在这些上面,再加一层,让语言的实现是在这一层而不是机器码。如果这个实现了,那么就可以跨界了,因为不需要依赖像从右至左入栈,然后依次弹出接收参数的底层实现方式,而是以更接近语言代码的方式。这是最基本的隔离,真的要实现完全跨界,A语言些的算法可以直接用在B语言的代码上,C语言的ADT可以被D语言使用,那需要大家都使用同一个标准,对象模型的标准。可以说是不可能完成的事。

0
抢小孩糖吃
抢小孩糖吃
我是这么理解这个问题的。语言的实现问题和底层,当前计算机语言还在独立的计算机去运行,node.js异步执行与Go的多核利用,已经很明确的标示了,底层结构以后不断会发生变化,必须现在是单机内存操作,新的hadoop或者集群化技术等内容都会改变现在的代码内容。问题来了,在相同语言下,由于硬件底层改变,需要对相同算法进行重写。我最近在看微软的CIL,他的方式就是语言的实现不是机器码,但我看现在的CIL并不具备可读性。我希望实现一种强可读性的CIL,并可以通过这个CIL来对转义各种语言。关于对象模型标准,由于在计算机中有MIPS,通过有限的元器件组合实现复杂功能。我认为对象模型标准库,是可以实现的。现在已经发现许多现代语言库的API结构,许多都模仿java,借鉴现有成果没有什么不好的。
句龙胤
句龙胤
关键是很多语言对“对象”的看法都不一样,C++的虚,还加上模板,Ruby的打开类,js根本就不用类而是原型,等等等等。每个语句基本的思想都不一样,有些地方甚至是巨大的差异。对象模型标准库,恐怕很难兼顾不同的语言。通过CIL来转义,这可行性很低,因为不同语言的代码差异很大,c++大量运用虚函数和模板,而java则完全以类为设计单位,js惯用闭包,甚至lisp,更加不同。恐怕是行不通的。
0
pantrick
pantrick

我觉得你想多了,一个人干着急是很费劲的,


0
d
dwcz
实际上,楼主已经说出下一代语言了,就是语义。现在的语言在语义上发展停滞,许多特性应该是在语义层实现,但为了快速编译都在语句层实现。造成代码在逻辑上实现封装,但在语言上非常零乱。一些本应隐藏细节,在应用时,反而显示出来了。
0
旁边白
旁边白
文字太多了,都懒得看了。一个观点: 设计一个程序语言真是太简单了,设计一个好的程序语言真是太难了
句龙胤
句龙胤
就是如此,新语言层出不穷,没有一个是好的设计
0
甘薯
甘薯

没看全太长了.

不管语言如何发展最终目的都是为了解决问题的.

楼主好像太过于在意细节了.

0
中山野鬼
中山野鬼

哈,大体看过了。不过我觉得楼主对几个内容还需要找点教科书,翻翻。个人感觉你的这方面的理解还不客观。(我不谈全面不全面)。一个是计算机组成原理,一个是编译原理。

例如,你这句话“现在的所有语言都在语义上有问题,比如我们到处都能看到类似a = 1这种语句,在我们的习惯上的认识上,这是赋值。但其实这不是赋值啊,这只是在说明a等于1。”

其实,a = 1 ,并不是说,是a 等于1,而是说,a是一个存储空间,将1放在存储空间中。a = a + 1 ,实际是,从a空间中取出所存储的数据,+1后,再放回a中。这是最基本的计算机运行原理。无论什么所谓高级的语言,如果存在 a = a + 1 这类的描述,本质上是一样的。

关于你认为,“好的程序语言”,我只能说,它是工具,来源于业务,服务于业务,评价在业务展开中,闭门的设计任何语言,都是shit。目前流行的大多数语言,都是面向一大类的程序设计任务具有优势。这也是为什么出现没有答案的辩论“哪门语言更好的”。

另外送楼主一句话,“系统设计”是“系统设计”,系统优化设计,是,系统优化设计。前者强调系统特性和内在模块的有机关联关系以及运行机理。后者强调特定目标下的系统特定指标。即便是设计一门语言,面对特定的业务领域,也需要先”系统设计“,再”系统优化设计“。

中山野鬼
中山野鬼
回复 @句龙胤 : 下一代?嗯。你能说下,是服务于谁的呢?是机器,还是业务?
句龙胤
句龙胤
回复 @中山野鬼 : 是期待下一代语言的出现,因为OOP已经几十年了,依然没有突破。从汇编到面向过程模块的,从过程到面向对象的,都没花多长时间,到了面向对象,一切都不同了,似乎再也难以突破了。
中山野鬼
中山野鬼
回复 @句龙胤 : 我理解你本文的内容是要实现一个计算机编程语言,哈。可能理解有误,至于其他语言,我就不了解了。。
句龙胤
句龙胤
我此处说的“但这其实不是赋值..”,是在说数学的=概念,不是说程序语言中的=,所以你误会了。
0
自由之信
自由之信
因为计算机教材上的对于对象等的教育是大大的错误,举例经常说,一个类来实现一个人,实现一个动物,这样的例子数不胜数,其实语言里面的类只能描述数据本身的抽象,而不是模型本身,就不应该拿高级的东西来做低级东西的例子,完全不能比,所以导致一种误解,认为计算机语言可以模型化现实的东西,抽象成数据可以,但是要说是等同,没可能的,计算机太低级了,还无法准确模拟任何现实的事物,更何况是生命。所以,语言建立在指令和系统之上,不应该把它费力的向太高级的概念靠拢,而是向抽象的数据和计算靠拢。
0
calvary
calvary
废话一大片。。  
返回顶部
顶部