加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 湛江站长网 (https://www.0759zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程 > 正文

编程的宗派

发布时间:2016-05-15 03:36:45 所属栏目:编程 来源:简书网
导读:总是有人喜欢争论这类问题,到底是“函数式编程”(FP)好,还是“面向对象编程”(OOP)好。既然出了两个帮派,就有人积极地做它们的帮众,互相唾骂和鄙视。然后呢又出了
副标题[/!--empirenews.page--]

编程语言 函数式编程 面向对象编程

总是有人喜欢争论这类问题,到底是“函数式编程”(FP)好,还是“面向对象编程”(OOP)好。既然出了两个帮派,就有人积极地做它们的帮众,互相唾骂和鄙视。然后呢又出了一个“好好先生帮”,这个帮的人喜欢说,管它什么范式呢,能解决问题的工具就是好工具!我个人其实不属于这三帮人中的任何一个。

面向对象编程(Object-Oriented Programming)

如果你看透了表面现象就会发现,其实“面向对象编程”本身没有引入很多新东西。所谓“面向对象语言”,其实就是经典的“过程式语言”(比如Pascal),加上一点抽象能力。所谓“类”和“对象”,基本是过程式语言里面的记录(record,或者叫结构,structure),它本质其实是一个从名字到数据的“映射表”(map)。你可以用名字从这个表里面提取相应的数据。比如point.x,就是用名字x从记录point里面提取相应的数据。这比起数组来是一件很方便的事情,因为你不需要记住存放数据的下标。即使你插入了新的数据成员,仍然可以用原来的名字来访问已有的数据,而不用担心下标错位的问题。

所谓“对象思想”(区别于“面向对象”),实际上就是对这种数据访问方式的进一步抽象。一个经典的例子就是平面点的数据结构。如果你把一个点存储为:

struct Point {    double x;    double y;  } 

那么你用point.x和point.y可以直接访问它的X和Y坐标。但你也可以把它存储为“极坐标”方式:

struct Point {    double r;    double angle;  } 

这样你可以用point.r和point.angle访问它的模和角度。可是现在问题来了,如果你的代码开头把Point定义为第一种XY的方式,使用point.x, point.y访问X和Y坐标,可是后来你决定改变Point的存储方式,用极坐标,你却不想修改已有的含有point.x和point.y的代码,怎么办呢?

这就是“对象思想”的价值,它让你可以通过“间接”(indirection,或者叫做“抽象”)来改变point.x和point.y的语义,从而让使用者的代码完全不用修改。虽然你的实际数据结构里面根本没有x和y这两个成员,但由于.x和.y可以被重新定义,所以你可以通过改变.x和.y的定义来“模拟”它们。在你使用point.x和point.y的时候,系统内部其实在运行两片代码,它们的作用是从r和angle计算出x和y的值。这样你的代码就感觉x和y是实际存在的成员一样,而其实它们是被临时算出来的。在Python之类的语言里面,你可以通过定义“property”来直接改变point.x和point.y的语义。在Java里稍微麻烦一些,你需要使用point.getX()和point.getY()这样的写法。然而它们最后的目的其实都是一样的——它们为数据访问提供了一层“间接”(抽象)。

这种抽象有时候是个好主意,它甚至可以跟量子力学的所谓“不可观测性”扯上关系。你觉得这个原子里面有10个电子?也许它们只是像point.x给你的幻觉一样,也许宇宙里根本就没有电子这种东西,也许你每次看到所谓的电子,它都是临时生成出来逗你玩的呢?然而,对象思想的价值也就到此为止了。你见过的所谓“面向对象思想”,几乎无一例外可以从这个想法推广出来。面向对象语言的绝大部分特性,其实是过程式语言早就提供的。因此我觉得,其实没有语言可以叫做“面向对象语言”。就像一个人为一个公司贡献了一点点代码,并不足以让公司以他的名字命名一样。

“对象思想”作为数据访问的方式,是有一定好处的。然而“面向对象”(多了“面向”两个字),就是把这种本来良好的思想东拉西扯,牵强附会,发挥过了头。很多面向对象语言号称“所有东西都是对象”(Everything is an Object),把所有函数都放进所谓对象里面,叫做“方法”(method),把普通的函数叫做“静态方法”(static method)。实际上呢,就像我之前的例子,只有极少需要抽象的时候,你需要使用内嵌于对象之内,跟数据紧密结合的“方法”。其他的时候,你其实只是想表达数据之间的变换操作,这些完全可以用普通的函数表达,而且这样做更加简单和直接。这种把所有函数放进方法的做法是本末倒置的,因为函数其实并不属于对象。绝大部分函数是独立于对象的,它们不能被叫做“方法”。强制把所有函数放进它们本来不属于的对象里面,把它们全都作为“方法”,导致了面向对象代码逻辑过度复杂。很简单的想法,非得绕好多道弯子才能表达清楚。很多时候这就像把自己的头塞进屁股里面。

这就是为什么我喜欢开玩笑说,面向对象编程就像“地平说”(Flat Earth Theory)。当然你可以说地球是一个平面。对于局部的,小规模的现象,它没有问题。然而对于通用的,大规模的情况,它却不是自然,简单和直接的。直到今天,你仍然可以无止境的寻找证据,扭曲各种物理定律,自圆其说地平说的幻觉,然而这会让你的理论非常复杂,经常需要缝缝补补还难以理解。

面向对象语言不仅有自身的根本性错误,而且由于面向对象语言的设计者们常常是半路出家,没有受到过严格的语言理论和设计训练却又自命不凡,所以经常搞出另外一些奇葩的东西。比如在JavaScript里面,每个函数同时又可以作为构造函数(constructor),所以每个函数里面都隐含了一个this变量,你嵌套多层对象和函数的时候就发现没法访问外层的this,非得bind一下。Python的变量定义和赋值不分,所以你需要访问全局变量的时候得用global关键字,后来又发现如果要访问“中间层”的变量,没有办法了,所以又加了个nonlocal关键字。Ruby先后出现过四种类似lambda的东西,每个都有自己的怪癖…… 有些人问我为什么有些语言设计成那个样子,我只能说,很多语言设计者其实根本不知道自己在干什么!

软件领域就是喜欢制造宗派。“面向对象”当年就是乘火打劫,扯着各种幌子,成为了一种宗派,给很多人洗了脑。到底什么样的语言才算是“面向对象语言”?这样基本的问题至今没有确切的答案,足以说明所谓面向对象,基本都是扯淡。每当你指出某个OO语言X的弊端,就会有人跟你说,其实X不是“地道的”OO语言,你应该去看看另外一个OO语言Y。等你发现Y也有问题,有人又会让你去看Z…… 直到最后,他们告诉你,只有Smalltalk才是地道的OO语言。这不是很搞笑吗,说一个根本没人用的语言才是地道的OO语言,这就像在说只有死人的话才是对的。这就像是一群政客在踢皮球,推卸责任。等你真正看看Smalltalk才发现,其实面向对象语言的根本毛病就是由它而来的,Smalltalk并不是很好的语言。很多人至今不知道自己所用的“面向对象语言”里面的很多优点,都是从过程式语言继承来的。每当发生函数式与面向对象式语言的口水战,都会有面向对象的帮众拿出这些过程式语言早就有的优点来进行反驳:“你说面向对象不好,看它能做这个……” 拿别人的优点撑起自己的门面,却看不到事物实质的优点,这样的辩论纯粹是鸡同鸭讲。

函数式编程(Functional Programming)

函数式语言一直以来比较低调,直到最近由于并发计算编程瓶颈的出现,以及Haskell,Scala之类语言社区的大力鼓吹,它忽然变成了一种宗派。有人盲目的相信函数式编程能够奇迹般的解决并发计算的难题,而看不到实质存在的,独立于语言的问题。被函数式语言洗脑的帮众,喜欢否定其它语言的一切,看低其它程序员。特别是有些初学编程的人,俨然把函数式编程当成了一天瘦二十斤的减肥神药,以为自己从函数式语言入手,就可以对经验超过他十年以上的老程序员说三道四,仿佛别人不用函数式语言就什么都不懂一样。

函数式编程的优点

函数式编程当然提供了它自己的价值。函数式编程相对于面向对象最大的价值,莫过于对于函数的正确理解。在函数式语言里面,函数是“一类公民”(first-class)。它们可以像1, 2, "hello",true,对象…… 之类的“值”一样,在任意位置诞生,通过变量,参数和数据结构传递到其它地方,可以在任何位置被调用。这些是很多过程式语言和面向对象语言做不到的事情。很多所谓“面向对象设计模式”(design pattern),都是因为面向对象语言没有first-class function,所以导致了每个函数必须被包在一个对象里面才能传递到其它地方。

函数式编程的另一个贡献,是它们的类型系统。函数式语言对于类型的思维,往往非常的严密。函数式语言的类型系统,往往比面向对象语言来得严密和简单很多,它们可以帮助你对程序进行严密的逻辑推理。然而类型系统一是把双刃剑,如果你对它看得太重,它反而会带来不必要的复杂性和过度工程。这个我在下面讲讲。

各种“白象”(white elephant)

所谓白象,“white elephant”,是指被人奉为神圣,价格昂贵,却没有实际用处的东西。函数式语言里面有很好的东西,然而它们里面有很多多余的特性,这些特性跟白象的性质类似。

函数式语言的“拥护者”们,往往认为这个世界本来应该是“纯”(pure)的,不应该有任何“副作用”。他们把一切的“赋值操作”看成低级弱智的作法。他们很在乎所谓尾递归,类型推导,fold,currying,maybe type等等。他们以自己能写出使用这些特性的代码为豪。可是殊不知,那些东西其实除了能自我安慰,制造高人一等的幻觉,并不一定能带来真正优秀可靠的代码。

纯函数

半壶水都喜欢响叮当。很多喜欢自吹为“函数式程序员”的人,往往并不真的理解函数式语言的本质。他们一旦看到过程式语言的写法就嗤之以鼻。比如以下这个C函数:

int f(int x) {     

      int y = 0;      

      int z = 0;      

      y = 2 * x;    

      z = y + 1;

      return z / 3;

  } 

很多函数式程序员可能看到那几个赋值操作就皱起眉头,然而他们看不到的是,这是一个真正意义上的“纯函数”,它在本质上跟Haskell之类语言的函数是一样的,也许还更加优雅一些。

(编辑:PHP编程网 - 湛江站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!