美文网首页
读编程范式游记有感

读编程范式游记有感

作者: 梧上擎天 | 来源:发表于2018-11-08 13:12 被阅读40次

    昨天趁着工作之余,看了左耳听风陈浩老师的《编程范式游记》系列文章,原文链接如下: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是写出好程序的关键所在!

    相关文章

      网友评论

          本文标题:读编程范式游记有感

          本文链接:https://www.haomeiwen.com/subject/uvmixqtx.html