| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 953 人关注过本帖
标题:编程的宗派 【转自: http://www.yinwang.org/blog-cn/2015/04/03/paradigm ...
只看楼主 加入收藏
longwu9t
Rank: 11Rank: 11Rank: 11Rank: 11
等 级:小飞侠
威 望:6
帖 子:732
专家分:2468
注 册:2014-10-9
结帖率:100%
收藏
 问题点数:0 回复次数:10 
编程的宗派 【转自: http://www.yinwang.org/blog-cn/2015/04/03/paradigms/】
  编程的宗派

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

  我个人其实不属于这三帮人中的任何一个。

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

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

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

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的代码,怎么办呢?

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

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

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

  软件领域就是喜欢制造宗派。“面向对象”当年就是这样扯着各种幌子,成为了一种宗派,给很多人洗了脑。很多人至今不知道自己所用的“面向对象语言”里面的很多优点,都是从过程式语言继承来的。每当发生函数式与面向对象式语言的冲突,都有面向对象的帮众拿出这些过程式语言早就有的优点来进行反驳,说:“你说面向对象不好,看它能做这个……” 拿别人的优点撑起自己的门面,这样的辩论纯粹是鸡同鸭讲,所以我懒得参加。

  函数式编程(Functional Programming)

  函数式语言一直以来比较低调,直到最近由于并发计算编程瓶颈的出现以及Haskell,Scala之类语言的流行,它忽然变成了一种宗派。被函数式语言洗脑的帮众,喜欢否定其它语言的一切,看低其它程序员。特别是有些初学编程的人,俨然把函数式编程当成了一天瘦二十斤的减肥神药,以为自己从函数式语言入手,就可以对其它经验超过他十年以上的老程序员说三道四,仿佛别人不用函数式语言就什么都不懂一样。函数式语言的拥鳖们认为这个世界本来应该是“纯”(pure)的,没有任何“副作用”。他们把一切的“赋值操作”看成低级弱智的作法。他们很在乎所谓尾递归,类型推导,fold,currying,maybe type等等。他们以自己能写出使用这些特性的代码为豪。

  可是殊不知,那些东西其实除了能自我安慰,以为自己高人一等,并不一定能带来真正优秀的代码。大量使用fold和currying的代码,写起来貌似很酷,可是读起来非常痛苦。很多人根本不明白fold的本质,却老喜欢用它,因为他们觉得那是函数式编程的“精华”,可以显示自己的聪明。然而,他们没有看到的是,其实fold包含的,只不过是在列表上做递归的“通用模板”,这个模板需要你填进去三个参数,就可以给你一个新的递归函数。所以每一个fold的调用,本质上都是一个完整的在列表上的递归函数定义。fold的问题在于,它定义了一个递归函数,却没有给它一个简单的名字。使用fold的结果是,每一次看到这个fold调用,你都需要重新读懂它的整个定义,才能琢磨出它到底是干什么的。比如,当你看到这句Haskell代码:

  foldr (+) 0 [1,2,3]

  你知道它是做什么的吗?也许你一秒钟之后就琢磨出,它的意思其实是对[1,2,3]里的数字进行求和,本质上相当于sum [1,2,3]。虽然只花了一秒钟,可你仍然需要琢磨。如果fold里面带有更复杂的函数,那么很有可能一分钟都琢磨不透。写起来倒没有费很大力气,可为什么我每次读这段代码,都需要看到+和0这两个跟自己的意图毫无关系的东西?为什么我需要搞清楚+, 0, [1,2,3]的相对位置以及它们的含义?这样的写法其实还不如写一个递归函数,给它一个有意义名字(比如sum),这样以后看到这个名字被调用,比如sum [1,2,3],你想都不用想就知道它要干什么。定义sum这样的名字虽然稍微增加了写代码时的工作,却给读代码的时候带来了很大的方便。所以为了写的时候简洁或者很酷而用fold,其实增加了读代码时的脑力开销。要知道代码被读的次数,要比被写的次数多很多,所以使用fold往往是得不偿失的。然而,被函数式语言宗派洗脑的人,却是看不到这一点的,他们太在乎显示给别人看,我也会用fold!

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

int f(int x) {
    int y = 0;
    int z = 0;
    y = 2 * x;
    z = y + 1;
    return z / 3;
      
}

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

  盲目鄙视赋值操作的人,也不理解“数据流”的概念。其实不管是对局部变量赋值还是把它们作为参数传递,其实本质上都像是把一个东西放进一个管道,或者把一个电信号放在一根导线上,只不过这个管道或者导线,在不同的语言范式里放置的方向和样式有一点不同而已!

  函数式语言的帮众没有看清楚的另一个重要的东西,是数据结构。数据结构的有些问题是“物理”和“本质”地存在的,真的不是你换个语言,或者换个风格,就可以搞定的。函数式语言的拥鳖们喜欢盲目的相信和使用列表(list),而没有看清楚它的本质以及它所带来的时间复杂度。列表带来的问题,不仅仅是编程的复杂性。不管你怎么聪明的使用它,很多性能问题是根本没法解决的,因为列表的拓扑结构根本就不适合用来干有些事情!

  从数据结构的角度看,Lisp所谓的list就是一个单向链表。你必须从上一个节点才能访问下一个,而这每一次“间接寻址”,都是需要时间的。在这种数据结构下,很简单的像length或者append之类函数,时间复杂度都是O(n)!为了绕过这数据结构的不足,所谓的“Lisp风格”告诉你,不要反复append,如果需要反复把元素加到列表末尾,那么应该先反复cons,然后再reverse一下。很可惜的是,当你同时又有循环或者递归的,就会发现cons+reverse的做法颠来倒去的,非常容易出错。这种方式用一次还可以,多几层嵌套之后,自己都把自己搞糊涂了。好不容易做对了,下次修改可能又会出错。然而就是有人喜欢显示自己聪明,喜欢自虐,迎着这类人为制造的“困难”勇往直前 :)

  富有讽刺意味的是,半壶水的Lisp程序员都喜欢用list,真正深邃的Lisp大师级人物,却知道什么时候应该使用记录(结构)或者数组。在Indiana大学,我曾经上过一门Scheme(一种现代Lisp方言)编译器的课程,授课的老师是R. Kent Dybvig,他是世界上最先进的Scheme编译器Chez Scheme的作者。我们的课程编译器的数据结构(包括AST)都是用list表示的。在课程最后,Kent对我们说:“你们的编译器已经可以生成跟我的Chez Scheme媲美的代码。然而Chez Scheme不止生成高效的目标代码,它的编译速度是你们编译器的700倍以上。它可以在5秒钟之内编译它自己!” 然后他透露了一点Chez Scheme速度之快的原因,而其中一个,就是因为Chez Scheme的内部数据结构根本不是list。在编译一开头的时候,Chez Scheme就已经把输入的代码转换成了数组一样的,固定长度的结构。

  后来在工业界的经验教训也告诉了我,数组比起链表,确实在某些时候有大幅度的性能提升。在什么时候使用链表,什么时候使用数组,是一门艺术。

  好好先生

  很多人避免“函数式vs面向对象”的辩论,于是他们成为了“好好先生”。这种人没有原则的认为,任何能够解决当前问题的工具就是好工具。也就是这种人,喜欢使用shell script,喜欢折腾各种Unix工具,因为显然,它们能解决他“手头的问题”。

  然而这种思潮是极其有害的,它的害处其实更胜于投靠函数式或者面向对象。没有原则的好好先生们忙着“解决问题”,却不能清晰地看到这些问题为什么存在。他们所谓的问题,往往是由于现有工具的设计失误。由于他们的“随和”,他们从来不去思考,如何从根源上消灭这些问题。他们在一堆历史遗留的垃圾上缝缝补补,妄图使用设计恶劣的工具建造可靠地软件系统。当然,这代价是非常大的。不但劳神费力,而且也许根本不能解决问题。

  所以每当有人让我谈谈“函数式vs面向对象”,我都避免说“各有各的好处”,因为那样的话我会很容易被当成这种毫无原则的好好先生。

  符号必须简单的对世界建模

  从上面你已经看出,我既不是一个铁杆“函数式程序员”,也不是一个铁杆“面向对象程序员”,我也不是一个爱说“各有各的好处”的好好先生。我是一个有原则的批判性思维者。我不但看透了FP和OOP的本质,而且看透了它们之间的统一关系。我编程的时候看到的不是表面的语言和程序,而是一个类似电路的东西。我看到数据的流动和交换,我看到效率的瓶颈,而这些都是跟具体的语言和范式无关的。

  在我的心目中其实只有一个概念,它叫做“编程”(programming),它不带有任何附加的限定词(比如“函数式”或者“面向对象”)。我的老师Dan Friedman喜欢把自己的领域称为“Programming Languages”,也是一样的原因。因为我们研究的内容,不局限于某一个语言,也不局限于某一类语言,而是所有的语言。在我们的眼里,所有的语言都不过是各个特性的组合。在我们的眼里,最近出现的所谓“新语言”,其实不大可能再有什么真正意义上的创新。我们不喜欢说“发明一个程序语言”,不喜欢使用“发明”这个词,因为不管你怎么设计一个语言,所有的特性几乎都早已存在于现有的语言里面了。我更喜欢使用“设计”这个词,因为虽然一个语言没有任何新的特性,它却有可能在细节上更加优雅。

  编程最重要的事情,其实是让写出来的符号,能够简单地对实际或者想象出来的“世界”进行建模。不管看起来多么酷的语言或者范式,如果必须绕着弯子才能表达程序员心目中的模型,那么它就不是一个很好的语言或者范式。有些东西本来就是有随时间变化的“状态”的,如果你偏要用“纯函数式”语言去描述它,当然你就进入了那些monad之类的死胡同。最后你不但没能高效的表达这种副作用,而且让代码变得比过程式语言还要难以理解。如果你进入另一个极端,一定要用对象来表达本来很纯的数学函数,那么你一样会把简单的问题搞复杂。Java的所谓design pattern,很多就是制造这种问题的,而没有解决任何问题。

  关于建模的另外一个问题是,你心里想的模型,并不一定是最好的,也不一定非得设计成那个样子。有些人心里没有一个清晰简单的模型,觉得某些语言“好用”,就因为它们能够对他那种扭曲纷繁的模型进行建模。所以你就跟这种人说不清楚,为什么这个语言不好,因为显然这语言对他是有用的!如何简化模型,已经超越了语言的范畴,在这里我就不细讲了。

  我设计Yin语言的宗旨,就是让人可以用最简单,最直接的方式来对世界进行建模,并且帮助他们优化和改进模型本身。
2015-04-04 20:36
TonyDeng
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:贵宾
威 望:304
帖 子:25859
专家分:48889
注 册:2011-6-22
收藏
得分:0 
这个观点是我同意的

授人以渔,不授人以鱼。
2015-04-04 20:51
冰镇柠檬汁儿
Rank: 16Rank: 16Rank: 16Rank: 16
来 自:北京
等 级:版主
威 望:120
帖 子:8078
专家分:6657
注 册:2005-11-7
收藏
得分:0 
javascript属于哪门哪派?

本来无一物,何处惹尘埃
It is empty at all here, Why pm 2.5 is so TMD high!
2015-04-04 21:01
TonyDeng
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:贵宾
威 望:304
帖 子:25859
专家分:48889
注 册:2011-6-22
收藏
得分:0 
我经常说,面向对象的基础是面向过程,后者是前者的辩证升级,否定之否定。但最难过关的是否定过程,很多人要么是否定了面向过程,要么是无法否定面向过程,前者表现为C++程序员看不起C程序员(当然反过来也是),后者则表现为把C++当C用,用面向对象的语法写C程序却当作是在面向对象编程。

面向对象,其实并不仅仅是封装、继承、多态那樣的纯技术概念,它的本质是面向所要处理的对象,比如你要处理一个坐标点,它自有其本质属性,如有两个或三个数的序列,之间是直角关系等等,这是这个事物和概念本身所固有的,面向对象,就是面向这个对象自身的这些固有特性及行为,把它模拟出来,也就是说,你的设计和观察角度必须站在对方的立场来做。面向对象之所以用于应用领域,是它面向使用者对象而设计的,用戶就是它所面向的对象,它所涉及的东西都是从用戶角度出发的。而面向过程,则是我程序写成怎样,你就得按我指定的方法做,容不得你发挥,比如scanf()中写了"%d,%d",你就必须在输入时带上逗号。两种思维的本质区别在这里,写出来的程序的体验也截然不同。写惯了,你是怎样的人,也是反映出来的。这也是我写程序总是考慮容错的原因。但很多人写程序是没那个习惯的,甚至还要说我的考慮多余,是不精致的代码,肯定了,你面向的是没自主性的死物机器,我面对的是活生生的人。


[ 本帖最后由 TonyDeng 于 2015-4-4 21:14 编辑 ]

授人以渔,不授人以鱼。
2015-04-04 21:08
诸葛欧阳
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
来 自:流年
等 级:贵宾
威 望:82
帖 子:2790
专家分:14619
注 册:2014-10-16
收藏
得分:0 
T版主竟然不用繁体字了

一片落叶掉进了回忆的流年。
2015-04-04 23:12
外部三电铃
Rank: 16Rank: 16Rank: 16Rank: 16
来 自:那一年
等 级:贵宾
威 望:57
帖 子:2013
专家分:7306
注 册:2007-12-17
收藏
得分:0 
有一定的道理,但也不能全信王垠的话,他思考问题容易走极端,他的知识可学,他的性格要引以为戒

那一年,苍井空还是处女
2015-04-04 23:43
love云彩
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
来 自:青藏高原
等 级:贵宾
威 望:53
帖 子:3663
专家分:11416
注 册:2012-11-17
收藏
得分:0 
大神之间的交流,菜鸟级果然看不懂,路过都不晓得说啥好了

思考赐予新生,时间在于定义
2015-04-05 01:52
wangnannan
Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18
等 级:贵宾
威 望:87
帖 子:2546
专家分:9359
注 册:2007-11-3
收藏
得分:0 
争论这些有什么用啊 管他是FP 还是OOP 搞程序的算法思想不能变 要顺应时代的发展 当然这一切要看哪个毛爷爷多
PHP JAVA c# VB VF 我都搞过 现在搞android我都不知道我是哪个门派的了 也有好处 比如android的service 我就爱用wcf 这就叫长处和长处结合

[ 本帖最后由 wangnannan 于 2015-4-7 10:00 编辑 ]

出来混,谁不都要拼命的嘛。 。拼不赢?那就看谁倒霉了。 。有机会也要看谁下手快,快的就能赢,慢。 。狗屎你都抢不到。 。还说什么拼命?
2015-04-07 09:58
xzlxzlxzl
Rank: 15Rank: 15Rank: 15Rank: 15Rank: 15
来 自:湖北
等 级:贵宾
威 望:125
帖 子:1091
专家分:5825
注 册:2014-5-3
收藏
得分:0 
电脑无码,心中有码,无门无派,运码无形!只要心中有大爱,代码即会无中生有,绵延不绝,阿门!
2015-04-07 11:47
TonyDeng
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:贵宾
威 望:304
帖 子:25859
专家分:48889
注 册:2011-6-22
收藏
得分:0 
以下是引用冰镇柠檬汁儿在2015-4-4 21:01:22的发言:

javascript属于哪门哪派?

BASIC派

授人以渔,不授人以鱼。
2015-04-07 16:31
快速回复:编程的宗派 【转自: http://www.yinwang.org/blog-cn/2015/04/03/par ...
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.046082 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved