左耳听风_031_30_编程范式游记1-_起源
你好,我是陈浩网民鹰,做耳朵耗子。
现在很多的文章和演讲呢都在谈架构,很少有人呢再会谈到编程范式。
但这些基础性和本质性的话题呢却是非常非常重要的那一方面呢我发现在一些语言争论上,有很多的人呢对编程语言的认识其实并不深。
那另一方面呢,通过编程语言的范式,我们不但可以知道整个编程语言的发展史,而且呢还能提高自己的编程技能啊,写出更好的代码。
我希望呢能通过这一系列的文章,带大家呢漫游一下各式各样的编程范式。
那这一系列文章中呢代码量很大,所以建议你啊看着文章听我讲解,我的文中呢也给你列了目录,方便你找到自己感兴趣的内容。
那这个经历呢可能会有一些漫长途中呢也会有各式各样的语言和代码。
但是我保证啊这个历程对于一个程序员来说是非常有价值的。
因为你不但可以对主流编程语言的一些特性有所了解,而且啊当我们到达终点的时候呢,你还能了解到编程的本质是什么。
那这一系列的文章中呢,会有各种语言的代码啊,其中有c呀c加加、python、 java scheme、 go、 javascript,还有prvlog等等。
所以呢如果想要跟上本文的前因后果,你需要对这几门比较主流的语言啊,多少有些了解。
而且呢你还需要在一线啊编写一段时间的代码,可能才能体会到这一系列文章的内涵。
那我根据每一篇文章所讲述的内容,把这一系列的文章呢分为四个部分。
那第一部分呢是泛型编程,在第一章到第三章啊,讨论了从c到c加加的泛型编程方法,并且系统的总结了编程语言中的类型系统和泛型编程的本质。
那第二部分呢是函数式编程。
在第四章和第五章讲述了函数式编程用到的技术和思维方式。
并且呢通过python和go修饰器的例子,展示了函数式编程下的代码扩展能力。
还有呢函数的相互和随意拼装带来的好处。
那第三部分呢是面向对象编程,在第六到第八章会讲述与传统的编程思想相反之处。
那面向对象设计中的每一个对象呢,都应该能够接受数据处理数据,并且啊将数据传达给其他的对象。
我还列举了面向对象编程的优缺点,基于圆形的编程范式和构元的委托模式。
那第四部分呢是编程的本质和逻辑。
编程在第九到第十一章,我们先探讨了编程的本质啊,也就是逻辑部分才是真正有意义的那控制部分呢只能影响逻辑部分的效率。
然后呢,我结合pro log语言介绍了逻辑编程的范式,最后呢对程序世界里的编程范式啊进行了总结,对比了他们之间的不同。
我会以每部分啊为一个发布单元,将这些文章呢陆续发表在专栏中。
那如果在编程范式方面啊,你有其他感兴趣的主题啊,欢迎留言给我。
那下面呢我们来说一说什么是编程范式。
编程范式的英文呢是programming paradise,那范的意思呢是模范。
所以范式呢也就是模式方法是一类典型的编程风格,指的是从事软件工程的一类典型的风格。
那编程语言发展到今天啊,出现了好多不同的代码编写方式。
但是不同的方式啊解决的都是同一个问题啊,那就是如何写出更为通用,更具可重用性的代码或者模块。
那如果你准备好了,那就和我一起来吧。
为了讲清楚这个问题呢,我需要从c语言开始讲起。
因为c语言啊历史悠久,而现在看到的几乎所有的编程语言啊,都是以c语言为基础来扩展的。
啊,不管是c加加java、 c sharf、 go、 python还是PHP啊,pro javascript、 lla还是shell等等。
那从c语言问世这四十多年以来呢,它影响了太多太多的编程语言,到现在呢还一直被广泛适用,不得不佩服他的生命力。
但是呢我们也要清楚的知道,大多数c like编程语言其实都是在改善c语言带来的问题。
那c语言有哪些特性呢?我简单来总结一下。
那第一呢c语言它是一个静态弱类型语言在使用变量的时候呢,需要声明变量的类型,但是类型间呢可以有隐式转换。
那第二呢,不同的变量类型可以用结构体struct组合在一起,以此啊来声明新的数据类型。
那第三点呢,c语言它可以使用type dif关键字来定义类型的别名,以此啊来达到变量类型的抽象。
那第四点呢,c语言它是一个有结构化程序设计,具有变量作用域和递归功能的过程式语言。
那第五呢,c语言传递参数啊一般是以值传递的方式也可以传递指针。
那第六个特性呢就是通过指针呢,c语言可以很容易的对内存进行低级控制。
当然这样也加大了编程复杂度。
最后第七点啊就是编程预处理啊,可以让c语言的编译更具有弹性。
比如可以跨平台那c语言的这些特性啊,可以让程序员在微观层面上写出非常精细和精确的编程操作,让程序员呢可以在底层和系统细节上非常自由和灵活,还有精准的控制代码。
但是呢在代码组织和功能编程上,c源的这些特效却没有那么美妙了。
我们先从c语言最简单的交换两个变量的swap函数说起,参看文中这段代码。
你可以想一想,这里呢为什么要传指针?那这里呢是c语言指针,因为如果你不用指针的话,那么参数呢就变成了传值。
也就是说函数的形参呢是调用实参的一个拷贝。
那函数里面对形参的修改呢无法影响实参的结果。
那为了要达到调用函数之后实参内容的交换,那必须要把实参的地址啊传递进来,也就是传始帧。
那这样呢,在函数里面做交换,那实际变量的值啊也被交换了。
但是呢这个函数最大的问题就是它呀只能给int值使用。
那这个世界上呢还有很多其他的类型啊,包括double float.那这个呢就是静态语言最糟糕的一个问题。
那与现实世界类比一下,数据类型呢,就好像是螺帽一样,有多种接口方式瓶口的、十字的、六角的等等。
而螺丝刀呢就像是函数或者是用来操作这些螺丝的算法或者代码。
我们发现啊这些不同类型的螺帽需要我们为它设备一堆不同的螺丝刀,而且呢它们还有不同的尺寸。
那尺寸呢就代表它是单字节的,还是多字节的,比如整形的int和long.那浮点数的float和double.那这样复杂度啊一下就提高了。
最终导致电工啊,也就是程序员工作的时候,需要携带文中图片中的这样的一堆工具。
那这个呢就是类型给编程带来的问题。
那要解决这个问题呢,我们还是要先看一下现实世界。
你应该见过文中图片里的这种经过优化的螺丝刀啊,上面的手柄是一样的。
你螺丝的动作呢也是一样的啊,只是接口不一样。
所以我每次看到这张图的时候呢,都在想这密密麻麻的看着有四十多种接口啊,不知道为什么人类世界要干出这么多的花样,你们这群人类啊究竟是要干什么呀?我们可以看到啊,无论是传统世界还是编程世界,我们都在看同一件事情,什么事儿呢?那就是使用一种更为通用的方式。
用另外的话说呢,就是抽象和隔离,让复杂的世界呢变得简单一些。
但是呢要做的抽象,对于c语言这种类型的语言来说呢,首先要拿出来讲的呢就是抽象类型,这就是所谓的泛型编程。
另外呢我们还要注意到在编程世界里呢,对于c语言来说啊,类型还可以转换编译器啊,会使用一切的方式来做类型转换。
因为类型转换有时候呢可以让我们的编程更方便一些,也可以让相近的类型可以做到一点点的泛型。
但是呢对于c语言的类型转换来说呢,是会出现很多问题的。
比如说呢传给我一个数组,那这个数组呢本来是double型的啊,或者是六十四位的long型。
但是如果我把这个数组的类型呢强转成int,那么呢就会出现很多的问题。
因为这样就会导致程序遍历数组的步长不一样。
比如一个类型为double长度为十的数组a那a下标二呢就意味着a加上size of double乘以二。
那如果你把a强转成int,那么a下标二呢就意味着a加上size of int乘以二。
我们知道size of double是八,而size of int呢是四。
于是呢就访问到了不同的地址和内存空间,这就会导致程序出现严重的问题。
好了,我们再来看一下c语言是如何实现泛型的那c语言的类型。
泛型呢基本上来说啊就是使用word星关键字啊,或者呢是使用宏定义。
你可以看一下文中使用了word星泛型版本的swap函数。
那这个函数呢几乎完全改变了int版函数的实现方式。
那这个实现方式呢有三个重点。
那第一点呢就是函数接口中呢增加了一个size参数。
那为什么要这么干呢?因为呢使用word星之后呢,类型被抽象掉了。
编析啊不能通过类型得到类型的尺寸了。
所以呢需要我们手动的加上一个表示类型长度的标识。
那第二呢,函数的实现中啊使用了memory copy函数。
那为什么要这么干呢?还是因为类型被抽象掉了,所以呢不能使用赋值表达式了。
那很有可能呢传进来的参数类型啊还是一个结构体。
所以呢为了要交换这些复杂类型的值啊,我们只能使用内存复制的方法。
那第三个重点呢就是函数的实现中啊,使用了一个time数组。
那这个呢就是交换数据的时候啊,需要用到的buffer用份儿啊,来做临时的空间存储。
于是呢新增的size参数使用的memory copy、内存拷贝,还有一个buffer,这些呢都增加了编程的复杂度。
那这个呢就是c语言的类型。
抽象所带来的复杂度的提升。
在提升复杂度的同时呢,我们发现还有其他的问题。
比如我想交换两个字符串数组啊,类型就是差星。
那么我们的swape函数的XY参数啊,是不是要用word星星了?那这样一来呢,接口就没法定义了。
那除了使用沃星来做泛省,在c语言中呢还可以使用宏定义来做泛省。
啊,你可以参考文中的这段代码,那用红定义带来的问题呢就是编译器啊是做字符串替换的。
因为宏是做字符串替换啊,所以就会导致代码的膨胀。
导致编译出来的执行文件呢,比较大不过呢,对于swap这个简单的函数来说呢,用word芯和宏替换都可以达到泛型的效果。
但是呢如果我们不是swaft,而是min或者max函数,那么宏器官的问题啊就会暴露的更多一些。
比如对于文中的这个实现了min函数的这个宏,那其中一个最大的问题呢就是有可能会重复执行。
比如对于这个命令函数来说呢,如果我给了两个参数啊,分别是i加加和j加加。
那我们的本意呢是比较完之后再对变量做累加。
但是呢因为宏替换的缘故会导致变量i或者j样被累加两次。
那另外呢同样对于这个main函数,如果我给的两个参数啊,分别是负函数和bar函数,那我们的本意呢是比较负函数和坝函数的返回值。
但是呢经过宏替换之后呢,富或者爸会调用两次,那这样呢会带来很多的问题。
另外呢你会不会觉得无论用哪种方式,这种发型啊,是不是太宽松了一些,完全不做类型检查啊,就是在内存上对考直接操作内存的这种方式啊,感觉是不是比较危险。
而且呢就像一个定时炸弹一样,不知道什么时候啊在什么条件下就爆炸了。
那从以上两个例子啊,我们可以发现,无论哪种方式啊接口都因为加入了size而变得复杂。
因为如果不加入size,那我们的函数内部啊就需要自己检查size.但是呢word星这种地址的方式啊是没办法得到science的。
而宏定义的方式呢,虽然不会把类型给隐藏掉,可以使用调用size of的方式得到size.但是如果类型是叉星,那么使用size of方式呢只能拿到指针类型的size,而不是直的size.另外呢对于不同类型啊,比如说double或者int,那应该使用谁的size呢?那是不是需要先转一下型呢?啊,这些啊都是问题。
所以呢这种泛型让我们根本没有办法检查传入参数的size,导致我们呢只能增加接口复杂度啊,加入一个size参数,然后把这个问题啊抛给调用者了。
那如果我们把这个事情变得更复杂,比如写一个四ars函数,那我就需要传入一个in的数组,a数组长度的size和目标只是target.然后呢,我想搜索这个target,那如果搜到了呢,就返回target对应的数组下标。
那搜不到呢就返回负一啊,就像文中我写的这样,那我们可以看到这个函数呢是int版本的那如果我们要把这个函数变成泛型呢,应该怎么变呢?就像上面swap函数那样,如果我们要把它变成泛型,我们需要变更并且复杂化。
函数的接口。
首先呢我们需要在函数接口上增加一个element site啊,也就是数组里面啊每个元素的size.那这样呢当我们遍历数组的时候啊,就可以通过这个sze正确的把指针啊移动到下一个数组元素。
那另外呢我还要加一个叫compare function的函数指针。
因为我要去比较数组里的每个元素和target是否要相等。
因为不同的数据类型比较的实限是不一样的。
比如整形比较呢用双等号就可以了。
但如果是一个字符串数组呢,那么比较呢就需要用string compare之类的函数。
那如果你要传一个结构体数索,比如account账号,那么比较两个数据对象是否一样就比较复杂了。
所以呢必须要自定义一个比较函数。
那最终呢我们得到了文中的serge函数的泛型法。
在这段代码中呢,我们没有使用mam compare函数。
这是因为啊如果这个数组是一个指针数组啊,或者说这个数组是一个结构体数组。
而这个结构体数组中呢有指针成员。
那我们想比较的是指针指向的内容,而不是指针这个变量自己。
所以呢用memory compare会导致我们在比较指针,而不是指针所指向的值。
那这样调用者呢就需要对int类型、字符串类型,还有业务的结构体类型分别提供不同的比较函数了。
你可以去文中看一下。
那我们的c语啊干成这个样子了,看上去还行。
但是呢上面这个设值函数啊只能用于数组这样的顺序型的数据容器啊,或者说是数据结构。
那如果说这个serge函数呢需要支持一些非顺序型的数据结构啊,比如说堆啊栈呢哈希表树,还有图,那么用c语言来干啊,基本上干不下去了。
对于像siarch这样的算法来说呢,数据类型的自适应问题啊就已经把事情搞得很复杂了。
但是呢数据结构的自适应还会把这个事的复杂度度再搞上几个数量级。
好了,这节课呢到这里就结束了。
最后让我们来简单的总结一下,那如果说程序等于算法加数据,我觉得对于c语言来说呢会有这么几个问题。
首先呢就是一个通用的算法,需要对所处理的数据的数据类型进行适配。
但是在适配数据类型的过程中呢,c语言啊只能使用word星啊或者宏替换的方式。
那这两种方式啊导致了类型过于宽松,并且呢还会带来很多其他的问题。
那第二个问题呢,就是如果我要适配数据类型,那么就需要让c语言啊在泛型中加入一个类型的size.这是因为我们识别不了被泛行后的数据类型,而c语言呢没有运行时的类型识别。
所以呢只能把这个工作抛给调用这个泛型算法的程序员来做了。
那第三个问题呢,就是算法其实是在操作数据结构,而数据呢则是放到数据结构中的。
所以呢真正的泛型除了适配数据类型之外呢,还要适配数据结构。
那最后这个事情呢会导致泛型算法的复杂度啊急剧上升,比如容器内存的分配和释放。
那不同的数据体呢可能有非常不一样的内存分配和释放模型。
再比如对象之间的复制,要把它存进来啊,我需要有一个复制的过程。
那这其中呢又涉及到是深拷贝还是浅拷贝。
另外呢就是在实现泛型算法的时候啊,你会发现自己一直在纠结哪些东西,应该抛给调用者处理,哪些呢?又是可以封装起来,如何平衡和选择,并没有定论,也不好解决。
那总体来说呢,c语言的设计目标啊是提供一种能以简易的方式编译处理低层内存,产生少量的机器码和不需要任何运行环境知识就能运行的编程语言。
那c语言呢也很适合搭配汇编语言来使用c语言,把非常底层的控制权啊都交给了程序员。
他有这么几个设计理念。
那第一呢是相信程序员。
那第二呢是不会阻止程序员啊做任何底层的事儿。
那第三呢是保持语言的最小和最简的特性。
第四呢是保证c语言最快的运行速度啊,哪怕是牺牲一致性。
从某些角度来说呢,c语言的伟大之处啊在于使用c语言的程序员。
在高级语言的特性之上呢,还能简单的做任何底层上的微观控制。
那这个呢就是c语言的强大和优雅之处。
也有人说c语言是高级语言中的汇编语言。
不过呢这只是在针对底层指令控制和过程式的编证方式。
而对于更高阶更为抽象的编程模型来说呢,c语言的这种既过程和底层的初衷设计方式啊就会成为它的短板。
因为啊在编程这个世界中呢,更多的编程工作是解决业务上的问题,而不是计算机的问题。
所以呢我们需要更贴近业务,更抽象的语言。
那说到这里呢,我想你可能会问,那c语言会去怎么解决这些问题呢?那简单来说呢,c语言它并没有解决这些问题,所以呢才有了后面c加加等其他语言。
在下一讲中呢,我也会和你聊一聊c加加是如何解决这些问题的那c议员诞生于一九七二年,到现在呢已经有四十五年的历史了。
所以它之后呢,c加加java、 c shap等语言前赴后继啊一浪高过一浪,都在试图解决各自时代里的特定的问题。
我们不能去否定某个语言。
但可以确定的是啊,随着历史的发展,每一门语言都还在默默的迭代,不断的优化和更新。
同时呢也会有很多新的编程语言,带着新的闪光耀眼的特性出现在我们面前。
再回过头来说变成范式呢,其实就是程序的指导思想,它也代表了这门语言的设计方向。
我们并不能说哪种范式更为超前,只能说各有千秋。
比如c语言呢就是过程式的编程语言。
那像c语言这样过程式编程语言的优点呢啊就是底层灵活,而且高效啊,特别适合开发运行较快,并且对系统资源利用率要求较高的程序。
但是我上面抛出的问题呢,他在后来呢也没有试图去解决。
因为编程范式的选择基本上已经决定了他的命运。
那我们怎么去解决上面c语言没有解决好的问题呢?请期待接下来的课程。