面向对象 vs. 函数式编程

Object oriented vs. functional programming — The Endeavour

OO makes code understandable by encapsulating moving parts.
OO通过切分封装能够更加易懂
FP makes code understandable by minimizing moving parts.
而FP通过最小化切分使得代码更加易懂。

前者试图将状态隐藏于对象接口之后,而后者试图通过尽可能的纯函数功能将状态切分再细直至最小化。(banq:两者侧重点还是不太一样)

大多数习惯于面向对象程序员喜欢将在OO基础上引入FP,但是作者认为:可能丧失一些纯函数式语言的独特特点,纯函数式语言更加易于调试,能够并行执行,这取决于他们没有副作用(side effects)

以下是我理解:因为将行为封装在对象中的方式带来最大问题就是行为功能的副作用,也就是某个对象的方法执行其主要功能作用外,必然带来其副作用,需要程序员非常小心应付,经常不经意就带来副作用,这样编程就很累,相反,如果我们直接从方法功能入手,就自然避免副作用,打个比喻:某个老板A手下有一个人才B很能干,可以独当一面,如果B一直位于A封装边界内,A可能让其偶尔做一些其不擅长的时间,这就是B的副作用;而如果让B独立新公司,完全放手,则对其没有牵制,也就没有副作用。

该文提出:100%纯函数式是否需要?James Hague认为不需要,只要85%就可以,权衡利弊,左右取之于中,提出functional in the small and OO in the large观点,我个人理解就是:在宏观战略Large方面使用面向对象;而在战术small方面使用函数式编程。

由于函数式编程在外观上类似以前面向过程编程中函数,所以,这个帖子也可以看成面向对象和面向过程分裂之后的再次组合的一个方式。


那么scala等一系列语言出现,正好印证这一方向?

>>因为将行为封装在对象中的方式带来最大问题就是行为功能的副作用,也就是某个对象的方法执行其主要功能作用外,必然带来其副作用
“副作用”的意思不是意料之外的行为,而是函数或方法的执行会改变程序的状态。比如:person.getAge()是无副作用的,因为它不改变程序的状态,因而没有并发、多线程等问题。

面向对象和函数式编程不是百分之几按照比例分配的问题,是如何结合的问题。CLOS在这方面应该算是做得相当不错的了。对副作用的处理方式,haskell的IO monad也是一个很好的解决方法。

按照我现在的想法,面向对象的概念需要由函数式语言给出形式描述,而不是人为规定,这才是正确的方法,就像CLOS里面做的那样。

百分比只是形象说法而已,只是说明以后对象行为的实现可能会大量用FP实现而已,而宏观上则采用OO的而已。(难道JDK7正预示未来,嘛,没所谓,工具而已)

在我之前看到的文章里,也有介绍过OO中设计模式本来就跟函数式思维相似,而他们其实是相通的。为啥C++能写出OO风格,OO能写出函数式风格,也就这个道理。不过也因为动静差异,造就很多不同的地方。(个人感觉而已,引用文章:http://blog.csdn.net/shendl/archive/2008/01/24/2064218.aspx)
[该贴被SpeedVan于2010-11-06 02:40修改过]

2010年11月06日 02:39 "SpeedVan"的内容
也有介绍过OO中设计模式本来就跟函数式思维相似,而他们其实是相通的 ...

其实面向对象和函数式编程是不同角度的思维,也就是屁股决定脑袋的事情,屁股坐在什么位置,就会有什么想法,面向对象是屁股坐在以人为本,为人服务的角度和位置;而函数式以及过去面向过程等是屁股坐在计算机机制(算法 数学等严谨物质)角度和位置。

因为人要自然地用软件,那当然面向对象和人靠得更近;而软件要在计算机等电子电路上运行,当然需要函数式以及其他各种精确性技术,如果以后软件要在神经细胞上运行,那你得有一套生物技术。

所以,从这个层面上看,面向对象是战略,函数式是战术执行。本来就不是两个层次的事情,咱们还是不要找出他们相通之处,位置角度不同而已。

在这里,我要强调的是:过去我们接受的教育和信息辐射都是侧重后者,也就是让人学会用计算机能听懂的思维去和他打交道,但是最终计算机软件还是要解决为人服务,不能认为强迫“所有人学会鸟语,忘记人话”是一种进步,是受教育的结果。这实际是也一种迷信的表现。

牛奶是给牛喝的,你可以喝牛奶,但是喝多了别都不知道有人奶了。


[该贴被banq于2010-11-06 11:34修改过]

有道理,确实硬要将函数式来表达一个完整世界(或完整领域)是一件很累的事情,它不是一种直观的表达,可以说它是依赖于电脑逻辑的,所以也就是为什么它可以达到所谓的“side effects”。但电脑逻辑却和人类直观思维背离,很大原因在于电脑是数的集合体。(当然,你认为一切皆数,这个可能就适合你了)

OO是一种直观思维,尽管它仍然依赖电脑,但它很大程度上,剥离数学概念的,它接近人类认知。可以说在人类和电脑中间,OO靠近人类,函数式靠近电脑,而把他们两个结合是为了更好的用电脑表示世界。但是由于电脑的不可改变性,导致人们妥协去靠近电脑来缩短之间的距离,这的确是一种方法(也想不到其他办法),同时代表性地逻辑哲学也浮出水面。

在我理解,之所以函数式没有“side effects”,原因在于那是电脑,那是需要逻辑。也就像banq所说的,为电脑服务,可以说人类思维迁就于电脑。而OO存在“side effects”因为是从人出发的,那是物质,那是对象,于banq所说的,是为人服务的。我觉得人脑厉害的地方在于宏观的扑捉,电脑厉害在于微观的计算,改变人脑的认识的方式未免有过了。其实可以反过来想一下,OO服务于人,所以对人基本没有“side effects”(基本一词保留意见,因为OO还是依赖电脑),但服务于电脑却有“side effects”;函数式服务于电脑,所以对电脑没有“side effects”,但对人有没有“side effects”,大家可以想想···


当然你可以把人脑当成电脑,当你的认知全是逻辑的时候,你就是一台电脑而已···

其实有时候可以想一下,到底是改变人类思维来迎合电脑,还是改变电脑迎合人类思维呢?尽管两种都是增强交流的方式,但那一个才是我们的本意呢?(说“迎合”可能会很那个,换一个词“更好地使用”,就舒服很多了,不过换汤不换药而已)

以上只是个人认知

与“函数式编程”相对应的是“命令式编程”,和“面向对象”有多大关系?
方向都没搞清楚还在扯什么。

TO is2wind
相对于“命令式编程”的是“声明式编程”···OO和FP到底如何比较,多看点国外文章。扯不扯懂的人自然会知···

OO显然是以数据为基础,封装了相关的算法,符合人脑理解事物的天性。
而fp则以算法为基础,把函数看成参数,甚至一切都是数据0和1,好是好,也只有电脑才能吃得消记得住。

然而,人脑不是电脑。我们用软件要解决的问题也常常是人类社会的问题,不是纯机器世界的问题。

面向过程虽然穿着函数这件马甲,与函数式编程应还是不一样的。在面向过程中,我们总是先定义变量,而后写过程,也就是说是围绕“事物”展开思考,尽管变量表示事物,可能缺失了事物本身应具有的丰富内涵。

函数式语言有什么优秀的特性,是面向对象所缺失的呢?lambda表达式、closure机制、callback机制还是什么呢?在面向类的语言中,这些特性有相应的实现方式。lambda表达式可以通过匿名类进行模拟,closure机制与类本身就心有灵犀,颇为相似(在Javascript可以通过闭包模拟类,但存在内存泄露问题),callback机制在“策略”、“观察者”“命令”等模式都有所体现。(从这个角度看,模式有时候可以看作超越本土语言限制从而具有另一种语言能力的一个途径。Peter Noving 有篇文章《Design Patterns in Dynamic Programming》展示了在一些动态语言中天生具有GoF23种模式其中16种模式的能力。)

在围绕“事物”的编程范式中,有面向变量(如C)、基于类的面向对象(如Java、C#,C++混合面向变量和基于类的面向对象的两种能力),基于原型的面向对象(如Javascript,当然Javascript不止具备这一能力 )、面向进程的(如Erlang),对"事物"不同粒度和方式的抽象,都有其合适的应用场景,在某些场景用变量思考方便一些、在某些场景用类思考方便一些、在某些场景用对象思考方便一些、在某些场景则用进程思考方便一些。

从直观上看,目前我的认识,语言对事物的抽象或仿生应该具备三种能力:1)事物可以同时具有状态和行为,这点Java、C等做得不错,只是类是一个相对静止的概念,也丢失了事物的一些内涵;2)事物可以自我成长,可以有新的记忆和习惯或放弃旧的记忆和习惯,也就说事物可以动态增删状态和行为,这点Javascript似乎具备了;3)事物可以自我独立运行,也就说事物应该具备进程的特性,“万物并育而不相害,道并行而不相悖”,这点Erlang做得不错,Erlang支持并行、分布、容错、热升级等特性,“无锁”的特点更容易将多核、分布计算的能力发挥得淋漓尽致。

Anders Hejlsberg预测语言的发展趋势是:声明式、动态式和并发式。Anders作为语言设计者,应有更深入的思考。而我(们)作为语言使用者,只能根据一些常识进行思考。但思考的结果似乎有些相似之处:动态式表达了事物自我成长的特性;并行式是表达了事物自我运行的特性。

至于声明式,一般将函数式语言归入声明式范式。之前的讨论改变我用声明式表达世界观的做法,现在我认为声明实际只是对方法论的一种描述,描述一种隐藏how的能力。从这个角度,语言的发展就是一个不断声明化的过程。汇编语言(徒步)相对于机器语言(爬走)是一种声明式表达,面向变量(自行车)相对汇编语言是一种声明式表达,面向对象(汽车)相对于面向变量是一种声明式表达。语言的发展好象交通工具的发展一般,总是希望可以使我们更快地到达目的地。

未来语言将在对“事物”更完善的抽象基础之上,即引入“动态式与并行式”;用更“声明式”的方式表达事物之间的“相互作用”。

此外语言的抽象程度,应与人的当下认知协相调,倘若语言的抽象并没有控制或降低解决问题的复杂度,则可能成为程序员的“心智负担”,反而制约了我们解决问题的能力。

未来还可能出现“元语言”,领域专家可按专业知识,使用“元语言”生成一种贴近领域术语与概念的语言。领域专家与程序员的关系就可能变得疏远,因为其可以直接使用生成后的语言对领域进行建模并实现。而程序员将再次被进行大的分化,一部分回到程序技术本身,一部分则跳到领域中去。上一次大的分化,是软硬技术的分离,这一次分化,将是领域与(程序)技术的分离。
[该贴被jdon007于2010-11-11 22:57修改过]

刚才看到一篇有趣的帖子:罗永浩:中医就是伪科学

好像和软件无关,但是我感觉这其中也有些有趣的类似性,中医最大的问题就是副作用,而面向对象的最大问题也是副作用;中医不是科学,是量化分析的反方向,是从天人合一围绕人的一种治病方式,由于其非量化,也就非精确化,必然带来很大副作用,面向对象估计也属于这种情况。

所以,面向对象结合函数式编程,能够克服OO的问题副作用,如果中医结合西方科学中量化精确化技术,就是其很好的发展方向(中学为体,西学为用)。

看来这个问题想明白了,能够很多问题都想明白,呵呵有意思。

在函数式的世界观中,描述事物的变量(或符号)是一次性的,赋值过一次就不能再赋值了。比如你令x=1, 就不能再x=x+1了,当x用完,就销毁。这是以“运动”(函数或逻辑表示)为第一性的世界观,事物不过是“过眼云烟”,数据驱动计算后,就立马销毁。在这种世界观下的指导下,自然是没有副作用,因为事物只能有一种状态(赋一次值),也就不难理解为什么纯函数对同样的输入只能有一种结果。

在我看来lambda、closure、callback等函数式范式的特性,还不能体现其世界观,只是一些操作层的方法,可以移植到以“事物”为中心(面向变量或面向对象)的范式中去,但也许不会产生革命性的变化。如果要真正结合两种不同的世界观,就需要对其进行明确的定位与分工。如Erlang,在外部,以“事物”为第一性,将“事物”抽象为“进程”;接着在“进程”内部,以“运动”为第一性,将“运动”抽象为“函数”。

现今主流的基于类的面向对象,对“事物”的抽象尚不完善,它只是绑定了变量与过程,但并没有刻画出“事物”动态生长、自我运行等特性。所谓“道法自然”,面向对象对事物的抽象或仿生,并没有尽如人意。

如果面向对象要与函数式结合,可以借用Erlang的设计智慧,在对象内部采用函数式表示,也许这样将带来一番新兴的气息,副作用也将消失(不过副作用真的有百害而无一利?这个现在我还不能确定。但可以肯定地是,没有副作用,事物内部状态的表示将会呈现出不同的形式)。

还要重复的观点是,即便引入函数式,“面向对象”对“事物”的抽象仍不完善,也许还需要引入动态和并行的能力,而如何有效、优雅地融合这些能力,这就需要语言设计师的智慧了。

其实我是这样理解,在一个特定的领域里面,为什么我们还会存在对象的变化,是因为这个领域里面还存在很多小领域。在一个足够小的领域里面,对象是相对静态的,而我们设计往往是由多个小领域组成,我把这样的小领域称作元领域。把所有小领域的东西都放到大领域,会使大领域的类变得臃肿,也就是所谓的万能类。实体boy在元领域A里面,并不代表不拥有在元领域B里面的行为(例如我懂写代码,并不代表我在一个不用写代码的领域里面就不懂写代码了)。为了更可读,可维护,我们把不需使用的行为隐性起来——以前想的是一个类干什么(没有考虑元领域),现在想的是一个类在某个元领域里面可以干什么(加入了元领域),于是DCI的思想出现了,而我所说的元领域就是Context。

lambda表达式和闭包,在jdk7就想加入了,最后还是可惜啊···(看见这两项通过的时候,多爽啊,最后居然不加入···真是期望越高,失望越高╮(╯▽╰)╭)
[该贴被SpeedVan于2010-11-12 20:26修改过]
[该贴被SpeedVan于2010-11-13 02:28修改过]

OO makes code understandable by encapsulating moving parts.
OO通过切分封装能够更加易懂
FP makes code understandable by minimizing moving parts.
而FP通过最小化切分使得代码更加易懂。

是否翻译成更好?
OO makes code understandable by encapsulating moving parts.
OO通过封装(状态)活动部分 使代码更加易懂
FP makes code understandable by minimizing moving parts.
FP通过最小化(状态)活动部分 使代码更加易懂。