怎样解决编程语言之间的差异性问题?
程序员文章站
2021-11-29 19:05:13
...
毫无疑问,不同的编程语言间存在着很多差异性。那么对于这种差异性开发者应如何解决?本文就来一探究竟。
以下为译文:
我一直在告诉别人:“编程非常了不起。”在你有任何想法的时候,都可以编写软件,然后愿望就实现了。这很真实。与建立物理的东西不同,首先你需要建立整个工厂,而软件的扩张相对非常容易。你可以找到所有已经编译好的组件,而且是免费的,拿来就可以用。建立好一段代码后,就可以重复使用无数次,而无需花钱。听起来很厉害的样子。
但有时候不是这样的。编程带给人的惊喜只是暂时的。在建立了很多代码以后,在写代码的过程中你会不断遇到让人迷惑的错误。一旦你习惯了特定语言和框架的模式后,一旦你需要第二种天性去掌握所选语言中非自然的语法时,编程的伟大之处就不复存在了。
更别提我们有无数种不同的编程语言。每当开发人员面对特殊语言的语法而深感沮丧时,他们都会想“为什么我们不能创建一种新的语言改正这个问题呢?”有些人还真的这么做了,很幸运的是自然选择已经淘汰了很多很差的语言(有时也有意外,比如至今PHP还存在)。一旦一门新的语言开始在一群开发者中流行起来,那么恭喜你 ,现在又出现了一个新的开发者社区,他们互相合作,努力让这门特殊的语言发展壮大。
每一种新语言的诞生所带来的创新,都能造福我们每一个人。但是有时也有不利的一面。有些人可能写了一些非常有用的开源JavaScript库,但是从事Python的开发者却完全没法用。他们不得不自己写一个Python版本的函数库,或者用JavaScript重写所有代码。再想想当前有多少种语言和框架。如果你不觉得这很荒谬的话,那只能说明或许你在软件开发这一行太长时间,已经习以为常了。
语言都包含些什么?
各种编程语言都在以下三个方面上有着很大的不同:语法、语义和标准库。
没错,我知道,我过于简化它们了,但是听我给你解释。
1. 语法
如果不遵循语法,那么你会在编辑器中看到各种弯弯曲曲的红线,而且你的代码也无法通过编译器或解释器。
JavaScript使用大括号,布尔型使用小写的true和false,用//表示行注释。
Python用缩进,布尔型用首字母大写的True和False表示,用#表示行注释。
Haskell又有完全不同的语法:
2.语义
所有编程语言都有大多数相同的特征:变量赋值、数字相加、字符串操作、调用函数、等等。
然而,每种语言都有特殊的思想,以特定的方式运行。可以将它们划分成不同的模式(命令式、面向对象、函数式),但是即便是两个相同模式的编程语言在细节上也是完全不同的。
在“声明类”,“调用函数”,或“定义参数的类型”时,你定义了程序的语义。有些语言遵循这样一套规则,而其他的遵循别的规则。比如:C++中声明的类可以延伸到多个类。当你使用“+”将数字和字符串加到一起的时候,根据语言的语义会得出不同的结果。一些编程语言会因为类型不匹配而导致编译失败,但是有些编程语言会自动将数字转换成十进制的字符串。
语法与语义的关系就相当于用单词(语法)来表达想法(语义)。你可以通过语言的语法来表达语义。
3.标准库
最后,每种语言都有各自的软件包,我们称之为“标准库”。
在Python中,你可以调用如下函数:
标准库是一门语言中重要的组成部分。它可以为语言带来活力,没有标准库,你无法简单地做出任何东西。
很讽刺的是,并没有“标准的标准库”。每个标准库基本上都不同于其他库:一些库只提供最低限度的方法,而有些库则提供非常广泛的函数,所以开发人员基本上不需要依赖任何第三方库。
基本的想法
以上我们介绍了一门语言的构成,接下来我有一个基本的想法:我们是否可以找到一种方法清晰地分割语法、语义和标准库呢?我们又如何实现这一想法呢?
第一步:只有程序员关心语法
我想解决的第一个问题就是语法。编译器和解释器拥有更加有效的方式表现代码,我们称之为抽象语法树。我们用代码描述的内容最终可以用如下抽象语法树表示:
如果仔细观察,你会发现上述语法树可能来自多个语言。是Python?是JavaScript?还是C++?这都无所谓:所有这些语言都拥有同一个语法树。
当然,现实的例子会更加复杂。这就是为什么我们用文本写代码的原因:更加紧凑,更加易于书写,还有更加易于阅读(有人可能有不同的看法)。从编程诞生的第一天,我们采用的就是这种方法,很少有人对此质疑。
对于一个更加现实的例子来说,抽象语法树会描述所选语言的语义(例如:类的定义)。但是拥有类似语义的语言之间还是可以共享同一个抽象语法树,并可以扩展到一定范围。这非常实用,因为你可以自动转化部分代码。
所以,我们可以把语法想象成抽象语法树之上的人类思维。代码可能并不会以文本的形式存储在任何地方,仅在文本编辑器内。如果你想在特殊的语言上使用不同的语法,也完全可以。这不会影响到别人。
我其实有点惊讶怎么没有一种工具可以将代码从一种语言转换成另一种语言,这完全可行啊。我猜肯定有人试过,但是放弃了,因为如果不将整个标准库转换过去的话是没有实用性的。很明显,我也在做这方面的尝试。
第二步. 将标准库抽象成API
API是一个非常高明的概念。每个软件都可以通过API与其他软件沟通。移动端的应用可以通过API与服务器交流。服务器可以通过API与数据库交流。每个人都可以通过API与他人对话。这是一件很酷的事情。
为什么我要在这里讨论API?因为这正是我们所需要的。API是语言的媒介。它们是一套语义,可以描述一个特殊代码模块对外提供的功能。无论是函数库,HTTP服务器,或是别的。
声明API的方式多种多样。可以是NPM上的JavaScript模块,并在README文件内提供API文档。也可以是代码中明确声明的API,比如TypeScript模块。也有可能并没有API的声明,也没有清晰的文档。
但是重要的是:API声明了代码模块的”对外接口“,你可以用其他语言重写模块内部的代码,但API不会发生改变。这就是API的魅力所在。虽然编程语言一团糟,但是API很酷。
前面我们提到了标准库,并说了各个语言都拥有完全不同的标准库。如果我们能想个办法将标准库抽象出来,做成干净利落的API,那么我们就可以解决这个问题。虽然在语义上,调用print("Hello")与Java调用System.out.println("Hello")不同,但是其实它们可以是同一个API。
我们有两种方法可以解决这个问题。要么我们让大家都不要再使用标准库了,转而使用我们的“API层”。或者我们可以让计算机自动推断你使用的代码。我并不看好“说服大家改变他们的方式”,所以我会选择后一种方法。
我们不用为编程语言的标准库中的每个函数都提供API。一般我们只可能用到标准库中的几个函数。我们可以自动将这些代码从一种语言转换到另一种语言。然后,我们只需要每个开发都用这些API替换具体的标准库的调用。不用担心,计算机依然需要你,至少现在需要。
第三步:把所有东西都做成API
现在我们有了干净的代码模块定义的纯粹的语义,并将与标准库的交互抽象成了API。
下一步做什么?创建API。
现今的代码库有多个文件构成,彼此之间通过“import语句”互相引用。这对于我们来说很便利,但是这也意味着我们需要在脑海中勾画代码库的结构。任何一个地方发生小的变化,都可能在不经意期间给别的地方带来破坏性的影响,尤其是如果我们没有做好自动测试的话。而且,代码库会不断增长,而编译的时间会逐渐加长。
也许有更好的方法解决这个问题。比如模块化就是个好办法。
我之前写过关于模块化的文章(点击这里查看:https://medium.com/@fwouts/the-zenc-master-plan-c693bf3b265e),基本上来说:每段独立的代码都应该抽象成API。我称之为模块。你无需在意一个具体的模块使用什么语言编写的。在写模块的时候,你不用导入这些文件。实际上,这时文件已不复存在。你可以直接使用API,它们会自动加载这些功能。
模块有什么好处?
我不确定第三步之后会发生什么,但是我觉得所有人都会很满意。
以下为译文:
我一直在告诉别人:“编程非常了不起。”在你有任何想法的时候,都可以编写软件,然后愿望就实现了。这很真实。与建立物理的东西不同,首先你需要建立整个工厂,而软件的扩张相对非常容易。你可以找到所有已经编译好的组件,而且是免费的,拿来就可以用。建立好一段代码后,就可以重复使用无数次,而无需花钱。听起来很厉害的样子。
但有时候不是这样的。编程带给人的惊喜只是暂时的。在建立了很多代码以后,在写代码的过程中你会不断遇到让人迷惑的错误。一旦你习惯了特定语言和框架的模式后,一旦你需要第二种天性去掌握所选语言中非自然的语法时,编程的伟大之处就不复存在了。
更别提我们有无数种不同的编程语言。每当开发人员面对特殊语言的语法而深感沮丧时,他们都会想“为什么我们不能创建一种新的语言改正这个问题呢?”有些人还真的这么做了,很幸运的是自然选择已经淘汰了很多很差的语言(有时也有意外,比如至今PHP还存在)。一旦一门新的语言开始在一群开发者中流行起来,那么恭喜你 ,现在又出现了一个新的开发者社区,他们互相合作,努力让这门特殊的语言发展壮大。
每一种新语言的诞生所带来的创新,都能造福我们每一个人。但是有时也有不利的一面。有些人可能写了一些非常有用的开源JavaScript库,但是从事Python的开发者却完全没法用。他们不得不自己写一个Python版本的函数库,或者用JavaScript重写所有代码。再想想当前有多少种语言和框架。如果你不觉得这很荒谬的话,那只能说明或许你在软件开发这一行太长时间,已经习以为常了。
语言都包含些什么?
各种编程语言都在以下三个方面上有着很大的不同:语法、语义和标准库。
没错,我知道,我过于简化它们了,但是听我给你解释。
1. 语法
如果不遵循语法,那么你会在编辑器中看到各种弯弯曲曲的红线,而且你的代码也无法通过编译器或解释器。
JavaScript使用大括号,布尔型使用小写的true和false,用//表示行注释。
function doSomething() { a = true; if (a) { ... // Do something. } }
Python用缩进,布尔型用首字母大写的True和False表示,用#表示行注释。
def doSomething(): a = True if a: ... # Do something.
Haskell又有完全不同的语法:
doSomething :: IO () doSomething = do let a = True if a then ... -- Do something. else return ()
2.语义
所有编程语言都有大多数相同的特征:变量赋值、数字相加、字符串操作、调用函数、等等。
然而,每种语言都有特殊的思想,以特定的方式运行。可以将它们划分成不同的模式(命令式、面向对象、函数式),但是即便是两个相同模式的编程语言在细节上也是完全不同的。
在“声明类”,“调用函数”,或“定义参数的类型”时,你定义了程序的语义。有些语言遵循这样一套规则,而其他的遵循别的规则。比如:C++中声明的类可以延伸到多个类。当你使用“+”将数字和字符串加到一起的时候,根据语言的语义会得出不同的结果。一些编程语言会因为类型不匹配而导致编译失败,但是有些编程语言会自动将数字转换成十进制的字符串。
语法与语义的关系就相当于用单词(语法)来表达想法(语义)。你可以通过语言的语法来表达语义。
3.标准库
最后,每种语言都有各自的软件包,我们称之为“标准库”。
在Python中,你可以调用如下函数:
- print():在控制台输出信息
- len():返回数组的长度
- 以及各种实用的模块,例如:json,threading,等等
标准库是一门语言中重要的组成部分。它可以为语言带来活力,没有标准库,你无法简单地做出任何东西。
很讽刺的是,并没有“标准的标准库”。每个标准库基本上都不同于其他库:一些库只提供最低限度的方法,而有些库则提供非常广泛的函数,所以开发人员基本上不需要依赖任何第三方库。
基本的想法
以上我们介绍了一门语言的构成,接下来我有一个基本的想法:我们是否可以找到一种方法清晰地分割语法、语义和标准库呢?我们又如何实现这一想法呢?
第一步:只有程序员关心语法
我想解决的第一个问题就是语法。编译器和解释器拥有更加有效的方式表现代码,我们称之为抽象语法树。我们用代码描述的内容最终可以用如下抽象语法树表示:
图:欧几里得算法的抽象语法树
如果仔细观察,你会发现上述语法树可能来自多个语言。是Python?是JavaScript?还是C++?这都无所谓:所有这些语言都拥有同一个语法树。
当然,现实的例子会更加复杂。这就是为什么我们用文本写代码的原因:更加紧凑,更加易于书写,还有更加易于阅读(有人可能有不同的看法)。从编程诞生的第一天,我们采用的就是这种方法,很少有人对此质疑。
对于一个更加现实的例子来说,抽象语法树会描述所选语言的语义(例如:类的定义)。但是拥有类似语义的语言之间还是可以共享同一个抽象语法树,并可以扩展到一定范围。这非常实用,因为你可以自动转化部分代码。
所以,我们可以把语法想象成抽象语法树之上的人类思维。代码可能并不会以文本的形式存储在任何地方,仅在文本编辑器内。如果你想在特殊的语言上使用不同的语法,也完全可以。这不会影响到别人。
我其实有点惊讶怎么没有一种工具可以将代码从一种语言转换成另一种语言,这完全可行啊。我猜肯定有人试过,但是放弃了,因为如果不将整个标准库转换过去的话是没有实用性的。很明显,我也在做这方面的尝试。
第二步. 将标准库抽象成API
API是一个非常高明的概念。每个软件都可以通过API与其他软件沟通。移动端的应用可以通过API与服务器交流。服务器可以通过API与数据库交流。每个人都可以通过API与他人对话。这是一件很酷的事情。
为什么我要在这里讨论API?因为这正是我们所需要的。API是语言的媒介。它们是一套语义,可以描述一个特殊代码模块对外提供的功能。无论是函数库,HTTP服务器,或是别的。
声明API的方式多种多样。可以是NPM上的JavaScript模块,并在README文件内提供API文档。也可以是代码中明确声明的API,比如TypeScript模块。也有可能并没有API的声明,也没有清晰的文档。
但是重要的是:API声明了代码模块的”对外接口“,你可以用其他语言重写模块内部的代码,但API不会发生改变。这就是API的魅力所在。虽然编程语言一团糟,但是API很酷。
前面我们提到了标准库,并说了各个语言都拥有完全不同的标准库。如果我们能想个办法将标准库抽象出来,做成干净利落的API,那么我们就可以解决这个问题。虽然在语义上,调用print("Hello")与Java调用System.out.println("Hello")不同,但是其实它们可以是同一个API。
我们有两种方法可以解决这个问题。要么我们让大家都不要再使用标准库了,转而使用我们的“API层”。或者我们可以让计算机自动推断你使用的代码。我并不看好“说服大家改变他们的方式”,所以我会选择后一种方法。
我们不用为编程语言的标准库中的每个函数都提供API。一般我们只可能用到标准库中的几个函数。我们可以自动将这些代码从一种语言转换到另一种语言。然后,我们只需要每个开发都用这些API替换具体的标准库的调用。不用担心,计算机依然需要你,至少现在需要。
第三步:把所有东西都做成API
现在我们有了干净的代码模块定义的纯粹的语义,并将与标准库的交互抽象成了API。
下一步做什么?创建API。
现今的代码库有多个文件构成,彼此之间通过“import语句”互相引用。这对于我们来说很便利,但是这也意味着我们需要在脑海中勾画代码库的结构。任何一个地方发生小的变化,都可能在不经意期间给别的地方带来破坏性的影响,尤其是如果我们没有做好自动测试的话。而且,代码库会不断增长,而编译的时间会逐渐加长。
也许有更好的方法解决这个问题。比如模块化就是个好办法。
我之前写过关于模块化的文章(点击这里查看:https://medium.com/@fwouts/the-zenc-master-plan-c693bf3b265e),基本上来说:每段独立的代码都应该抽象成API。我称之为模块。你无需在意一个具体的模块使用什么语言编写的。在写模块的时候,你不用导入这些文件。实际上,这时文件已不复存在。你可以直接使用API,它们会自动加载这些功能。
模块有什么好处?
- 可以鼓励大家考虑设计:首先你需要设计API
- 可以降低认知的开销:你仅需要“填空”
- 简化测试:你只需测试API,并可以模拟所有的依赖性
- 世界会变得更加美好:没有了语言之间的壁垒,没有了巨大的代码库;程序员更加快乐,客户更加快乐
我不确定第三步之后会发生什么,但是我觉得所有人都会很满意。
上一篇: 深入理解Android消息机制