昨天趁着工作之余,看了左耳听风陈浩老师的《编程范式游记》系列文章,原文链接如下:https://time.geekbang.org/column/article/301。看完有一种把之前积攒的零散的知识点用一条线穿起来的感觉。
我是从大学接触的C,到工作开始写的java,页面撸的js,后来大数据要用到的scala和python,学完多种编程语言之后,看到的大多是各个语言中的差异,至于高级编程语言的相同点呢,似乎有那么一点点的感觉,但无法系统的总结出来,作者这个系列算是解惑了我的疑问,提升了眼界。我整理了一下笔记,因为无法体会全部内涵所以笔记大多为摘抄,希望大家订阅陈浩老师的内容进行学习。最后自己也突发了一些感想希望分享给大家。
个人感想
在我刚学习java的时候,学到“抽象”这个概念时一直觉得这未免太抽象了,现实世界如此现实干嘛需要这种抽象的定义“抽象”呢?我们可以使用举例子或者比喻来解释每个概念嘛。
随着我接触了不同的领域,像心理学、金融学、经济学、技术到了瓶颈期又开始学习数学,然后脑子就变的混沌了起来,觉得这世上要学的东西太多了,没有一点规律。
真的没有规律吗?过了几年再回过头看“抽象”这个概念,我发现每个领域的知识从无到有慢慢发展,发展发展着就会有一个趋势,那就是从具象到抽象的过程。
比如人类的最先出现的语言,这其实就是具体事物的抽象化描述,我看到了一匹马,我在和别人讨论的时候,不必非要牵过来一匹马,而是可以用“马”这个字来代替,大家就都明白了。
数字也是一样,0这个数字就是“没有”的抽象。
艺术也是一样,古人从追求逼真的写真,到毕加索抽象派大家。
软件开发也是一样,不同语言之间目的抽象到最后,都是要用来解决问题的,解决什么?解决如何写出通用、优雅、可维护的高效代码。什么声明式,命名式,函数式,面向对象范式等实现的方式只是技术手段或者说是技术工具。
最后感想:“抽象”的看待问题,需要系统的看某个领域的发展史,只有看到从具象化到抽象化的过程,才有机会洞察发现到事情的本质。
笔记备忘
序言
编程语言发展到今天,出现了好多不同的代码编写方式,但不同的方式解决的都是同一个问题,那就是如何写出更为通用、更具可重用性的代码或模块
C语言起源
C语言是高级语言的起源,其他后续语言大多都能看到它的影子,但C对于范式来说,缺乏解决办法,只能用void*或者定义宏。这种方式太宽泛,直接操作内存,而且无法获取size。因为是操作内存的,所以操作非顺序型数据结构就会相当复杂。
整体上说C语言这样的过程式编程语言优点是底层灵活而且高效,特别适合开发运行较快且对系统资源利用率要求较高的程序
C++的泛型编程
C++的出现就是满足C语言满足不了的问题。例如
- 用引用来解决指针的问题。
- 用namespace来解决名字空间冲突的问题。
- 通过try-catch来解决检查返回值编程的问题。
- 用class来解决对象的创建、复制、销毁的问题,从而可以达到在结构体嵌套时可以深度复制的内存安全问题。
- 通过重载操作符来达到操作上的泛型。(比如,消除上一篇文章中提到的比较函数cmpFn,再比如用>>操作符消除printf()的数据类型不够泛型的问题。)
- 通过模板template和虚函数的多态以及运行时识别来达到更高层次的泛型和多态。
用RAII、智能指针的方式,解决了C语言中因为需要释放资源而出现的那些非常ugly也很容易出错的代码的问题。 - 用STL解决了C语言中算法和数据结构的N多种坑。
类型系统和泛型本质
程序语言的类型系统提供功能如下:
- 安全性,编译器会检查 “helloword” + 3 这种不同数据类型间的操作问题。
- 便于编译器优化。静态类型语言声明,可以让编译器知道程序员意图,进行优化。例如我们指定int类型,那么编译器就会用4个字节的倍数进行对其,高效执行命令。
- 代码的可持续性。易读可维护,如接口的声明。
-
抽象化。类型允许程序设计者对程序以较高层次的方式思考,而不是烦人的低层次实现。例如,我们使用整型或是浮点型来取代底层的字节实现,我们可以将字符串设计成一个
值,而不是底层的字节的数组。从高层上来说,类型可以用来定义不同模块间的交互协议,比如函数的入参类型和返回类型,从而可以让接口更有语义,而且不同的模块数据交换
更为直观和易懂。
慢慢的世界出现两种类型的预言。一类是静态类型的语言也叫强类型语言,如C、C++、Java,一种是动态类型语言也叫弱类型语言,如Python、 PHP、 JavaScript等。
哪怕是可随意改变的变量类型的动态语言,我们在读代码的过程中也需要模拟出运行的类型。所以每种语言都需要有一套类型检查系统。静态语言的支持者会说编译器会帮我们找到这些问题,而动态语言的支持者则认为,静态语言的编译器也无法找到所有的问题,想真正提前找到问题只能通过测试来解决。其实他们都对。
要了解泛型的本质,就需要先了解类型的本质。
- 类型是对内存的一种抽象。不同的类型,会有不同的内存布局和内存分配策略。
- 不同的类型,有不同的操作。对于特定的类型,也有特定的一组操作。
所以要做到泛型,我的理解就是进行标准化的规范:
- 标准化掉类型的内存分配、释放和访问。
- 标准化掉类型的操作。比如:比较操作, I/O操作,复制操作……
- 标准化掉数据容器的操作。比如:查找算法、过滤算法、聚合算法……
- 标准化掉类型上特有的操作。需要有标准化的接口来回调不同类型的具体操作……
我理解其本质就是 —— 屏蔽掉数据和操作数据的细节,让算法更为通用,让编程者更多地关注算法的结构,而不是在算法中处理不同的数据类型。
函数式编程
比C++更抽象的泛型方法。
对于函数式编程来说,其只关心, 定义输入数据和输出数据相关的关系,数学表达式里面其实是在做一种映射(mapping),输入的数据和输出的数据关系是什么样的,是用函数来定义的。
函数式编程有以下特点:
- stateless:函数不维护任何状态。函数式编程的核心精神是stateless,简而言之就是它不能存在状态,你给我数据我处理完扔出来,里面的数据是不变的。
- immutable:输入数据是不能动的,动了输入数据就有危险,所以要返回新的数据集。
好处:
- 惰性求值
- 确定性
技术:
- frst class function(头等函数) :这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建、修改,并当成变量一样传递、返回,或是在函数
中嵌套函数。 - tail recursion optimization(尾递归优化) : 我们知道递归的害处,那就是如果递归很深的话 stack受不了,并会导致性能大幅度下降。因此,我们使用尾递归优化技术——每
次递归时都会重用stack,这样能够提升性能。当然,这需要语言或编译器的支持。 Python就不支持。 - map & reduce :这个技术不用多说了,函数式编程最常见的技术就是对一个集合做Map和Reduce操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语
言需要使用for/while循环,然后在各种变量中把数据倒过来倒过去的)这个很像C++ STL中foreach、 fnd_if、 count_if等函数的玩法。 - pipeline(管道) :这个技术的意思是,将函数实例成一个一个的action,然后将一组action放到一个数组或是列表中,再把数据传给这个action list,数据就像一个pipeline一样
顺序地被各个函数所操作,最终得到我们想要的结果。 - recursing(递归) :递归最大的好处就简化代码,它可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
- currying(柯里化) :将一个函数的多个参数分解成多个函数, 然后将函数多层封装起来,每层函数都返回一个函数去接收下一个参数,这可以简化函数的多个参数。
在C++中,这很像STL中的bind1st或是bind2nd。
注:有一些人可能会觉得这会对性能造成影响。其实,这个劣势并不见得会导致性能不好。因为没有状态,所以代码在并行上根本不需要锁(不需要对状态修改的
锁),所以可以拼命地并发,反而可以让性能很不错。比如: Erlang就是其中的代表。 - higher order function(高阶函数) :所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一
样。这个技术用来做 Decorator 很不错。
函数式编程关注的是: describe what to do, rather than how to do it。于是,我们把以前的过程式编程范式叫做 Imperative Programming – 指令式编
程,而把函数式编程范式叫做 Declarative Programming – 声明式编程。
面向对象编程
函数式编程是没有状态的,如果想要有状态来保存数据,就需要用到面向对象的范式了。
面向对象编程是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单
元,将程序和数据封装其中,以提高软件的可重用性、灵活性和可扩展性,对象里的程序可以访问及修改对象相关联的数据。在面向对象编程里,计算机程序会被设计成彼此相关的对象。
23个经典的设计模式,基本上就是说了两个面向对象的核心理念。
- "Program to an 'interface', not an 'implementation'."
- 使用者不需要知道数据类型、结构、算法的细节。
- 使用者不需要知道实现细节,只需要知道提供的接口。
- 利于抽象、封装、动态绑定、多态。
- 符合面向对象的特质和理念。
- "Favor 'object composition' over 'class inheritance'."
- 继承需要给子类暴露一些父类的设计和实现细节。
- 父类实现的改变会造成子类也需要改变。
- 我们以为继承主要是为了代码重用,但实际上在子类中需要重新实现很多父类的方法。
- 继承更多的应该是为了多态。
和函数式编程来比较,函数式强调于动词,而面向对象强调于名词,面向对象更多的关注于接口间的关系,而通过多态来适配不同的具体实现。
基于原型的编程范式
其本质也是面向对象的,但是没有class化,直接使用对象。以JavaScript举例,面向对象里面要有个Class。但是JavaScript觉得不是这样的,它就是要基于原型编程,就不要Class,就直接在对象上改就行了。基于编程的修改,直接对类型进行修改。
本质就是一种委托的方式
编程的本质
**Program = Logic + Control + Data Structure
是处理什么(logic),怎么做(control),沟通方式(数据结构)
所有的范式其实都是在处理上面这三个问题如:
- 函数式中的map、reduce、filter。他们都是一种控制,整体lambda表达式才是要解决的问题,他们俩形成一个算法,把数据放到数据结构中处理,成为程序。
- 面向对象中依赖的接口而不是实现一样。
- control可以标准化,如遍历,查找,多线程,并发,异步等,都可以标准化。
- 因为control要处理数据,所以也要标准化泛型数据结构。
- control还要处理用户的业务逻辑,就是logic,所以我们通过标准化接口和协议来实现,将logic和control进行适配。
如何分离control和logic呢?我们可以使用下面的这些技术来解耦。
- State Machine
- 状态定义
- 状态变迁条件
- 状态的action
- DSL – Domain Specifc Language
- HTML, SQL, Unix Shell Script, AWK,正则表达式……
- 编程范式
- 面向对象:委托、策略、桥接、修饰、 IoC/DIP、 MVC……
- 函数式编程:修饰、管道、拼装
- 逻辑推导式编程: Prolog
有效地分离Logic、Control和Data是写出好程序的关键所在!
网友评论