




已阅读5页,还剩4页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
学会像一个函数式程序员那样思考在开始进入正题之前,我们先来做一个比喻。假设你是一个伐木工人,你拥有一把这个森林里最好的斧子,而它使也你成为了当地最有生产力的伐木工人。 某一天,有人向你展示并称赞了一个新的伐木工具-电锯。由于销售人员是一个非常能推销的人,所以你买了一把电锯回来,尽管你并不知道如何去用。于是你尝试像以前砍树那样的来回摆动去锯树。并且你很快得出了一个结论这个新式的电锯毫无用处,于是你又重新拿起斧子去伐木。一直到有人过来并给你演示了如何去运转电锯,你才明白这里的不同。你可能联想到了用函数式编程来代替故事中的电锯。但是问题在于函数式编程是一种全新的编程模式,而不是一门新的语言,语法只是一个细节问题。而最不同的地方是要如何以不同的方式去思考。而我作为一名“电锯演示者”和一个函数式程序员来到了这里。欢迎来到函数式思维专栏。这个系列将探索函数式编程的话题,但是并不仅仅局限在函数式编程语言有关的内容上。正如我描绘的那样,以函数式的方法来写代码涉及到了设计,权衡,代码重用和其他一系列的观点。我会尝试着以Java(或是类Java语言)的方式尽可能多的展示函数式编程的概念, 进而演示一些其他语言的能力-那些Java不具有的能力。当然我不会直接切入的非常深,然后讨论一些时髦的事物。取而代之的是,我会逐渐演示一种新的思考问题的方式(或许你已经在某些地方用了,但还没有意识到)。在接下来的两部分里,你可以把它当作是有关于函数式编程话题的一个旅行。其中的某些概念将会有大量的细节,在这个系列中我会用更多的情景和细节去描述。在旅程开始前,我将带你看一下一个相同问题的两个不同实现,一个用传统的方式来写,另一个使用更多的函数式方式。数字归类谈论两种不同的编程模式,你必须用代码来做比较。第一个例子是我另一本书The Productive Programmer和测试驱动设计1,2两篇文章中的一个变体。我选取了少量的代码,因为在这两篇文章里已经深入的分析了这段代码。这些文章对这个设计所做的称赞并没有错,但我想在这里进一步提供一个不同的设计意图。问题的需求是这样的:假设给定任意一个正整数都大于1,你必须按照完美的,过剩的和不足的进行归类。一个完美数正好是它所有整除因子的总和。同样地,一个过剩数的所有整除因子总和大于该数,而一个不足数的所有整除因子总和小于该数。快速数字归类器列表1中的类(NumberClassifier)满足所有这些需求:1. publicclassClassifier62. privateSet_factors;3. privateint_number;4. 5. publicClassifier6(intnumber)6. if(number1)7. thrownewInvalidNumberException(Cantclassifynegativenumbers);8. _number=number;9. _factors=newHashSet();10. _factors.add(1);11. _factors.add(_number);12. 13. 14. privatebooleanisFactor(intfactor)15. return_number%factor=0;16. 17. 18. publicSetgetFactors()19. return_factors;20. 21. 22. privatevoidcalculateFactors()23. for(inti=1;i_number;48. 49. 50. publicbooleanisDeficient()51. returnsumOfFactors()-_number_number;52. 53. 54. publicstaticbooleanisPerfect(intnumber)55. returnnewClassifier6(number).isPerfect();56. 57. 这段代码有几处地方需要关注一下:它拥有大范围的测试(有一部分我是为了讨论测试驱动开发而写的)注:这条所说的测试位于作者另一篇文章中。这个类由大量的紧耦合方法组成,在它的构造函数中拥有测试驱动开发的边际效应。在calculateFactors()方法里内嵌了性能优化算法。这个类的主体是由采集因子组成,因此我可以在之后对它们进行求和并进行最终的归类。整除因子总是以成对的形式被获取。例如,如果这个数是16,当我采集的因子为2时,我就能得到另一个因子为8,因为8x2=16。如果我获得的因子是成对的,那么我只需要去检查那些有平方根的数,这就是calculateFactors()方法所做的事情。更多的功能归类使用相同的测试开发技术,我创建了一个修改后的版本。列表2,更丰富的功能数字归类器1. publicclassNumberClassifier2. 3. staticpublicbooleanisFactor(intnumber,intpotential_factor)4. returnnumber%potential_factor=0;5. 6. 7. staticpublicSetfactors(intnumber)8. HashSetfactors=newHashSet();9. for(inti=1;i=sqrt(number);i+)10. if(isFactor(number,i)11. factors.add(i);12. factors.add(number/i);13. 14. returnfactors;15. 16. 17. staticpublicintsum(Setfactors)18. Iteratorit=factors.iterator();19. intsum=0;20. while(it.hasNext()21. sum+=(Integer)it.next();22. returnsum;23. 24. 25. staticpublicbooleanisPerfect(intnumber)26. returnsum(factors(number)-number=number;27. 28. 29. staticpublicbooleanisAbundant(intnumber)30. returnsum(factors(number)-numbernumber;31. 32. 33. staticpublicbooleanisDeficient(intnumber)34. returnsum(factors(number)-numbernumber;35. 36. 这两个版本的类尽管差别细微但是很重要。最主要的区别是例2的版本缺少了状态共享。消除状态共享在函数式编程中是比较受欢迎的一种抽象手法。作为跨方法共享状态的替代方案,我采用直接调用的方式来消除状态共享。从设计的角度来说,它让factors()方法变的更长,但是它也防止了factors字段暴漏到方法之外。注意,例2是完全由静态方法组成的。在方法间不存在知识共享的问题,因此我可以在更少函数范围上做封装。一旦你给它们输入参数和期待值,这些方法都会工作的很好(这个是一个纯函数例子,这个概念我在将来会进一步探索它)。函数函数式编程属于一个宽泛的计算机科学范畴,它已经受到了极大的关注。有新的基于JVM上开发的函数式语言(如scala和clojure)和框架(如Functional Java和Akka),它们都声称能够带来更少的缺陷,更高的生产力,更易读,更赚钱等等。相比驻足门外的去解决函数式编程这一大话题,我更愿意将注意力放在一些概念以及这些概念衍生出来的话题上。函数式编程的核心就是函数, 正如在面向对象语言里面类是主要的抽象那样。函数形成了处理过程的基础,同时它具有其他传统语言没有的一系列特性。高阶函数高阶函数可以将其他函数作为参数或者作为返回结果。这在Java语言中是无法想像的。最接近的方案是你使用一个类(通常是匿名类)作为执行方法的“持有者”。Java没有独立的函数(或方法),因此它们不能作为返回值或参数出现。这个能力对函数式语言来说很重要的原因有两点:第一,拥有高阶函数就意为着你可以在如何连结语言元素上作出一个假设。例如,你可以构建一个机制来消除一个类继承体系上的一大堆方法,通过遍历列表并对每一个元素应用一个(或多个)高阶函数来实现。(我将展示一个简短的例子给你)第二,通过将函数作为返回值,你有机会去创建一个高动态,适应性的系统。通过使用高阶函数我们就可以使问题服从于方案,但高阶函数对函数式语言来说并不是唯一的。因此,当你在使用函数式思维的时候, 你解决问题思路就会不一样。考虑一下列表3中的例子,一个保护数据访问的方法:1. publicvoidaddOrderFrom(ShoppingCartcart,StringuserName,2. Orderorder)throwsException3. setupDataInfrastructure();4. try5. add(order,userKeyBasedOn(userName);6. addLineItemsFrom(cart,order.getOrderKey();7. completeTransaction();8. catch(Exceptioncondition)9. rollbackTransaction();10. throwcondition;11. finally12. cleanUp();13. 14. 列表3中的代码执行初始化等具体任务,如果所有操作都成功就完成事务,反之回滚,并在最后清理掉资源。很明显,代码有一部分可以被重用,并且我们在面向对象语言中也常常创建这样的结构。在这个例子中,我组合使用了两个“四人团”的设计模式:模版方法和命令模式。模版方法建议我应该移动一些通用的模版代码到继承体系中,并推迟算法细节到子类。命令行模式提供了一个方法以众所周知的执行语义来封装行为到类,列表4就是列表3代码应用这两个模式之后的样子:列表4 重构顺序后的代码1. publicvoidwrapInTransaction(Commandc)throwsException2. setupDataInfrastructure();3. try4. c.execute();5. completeTransaction();6. catch(Exceptioncondition)7. rollbackTransaction();8. throwcondition;9. finally10. cleanUp();11. 12. 13. 14. publicvoidaddOrderFrom(finalShoppingCartcart,finalStringuserName,15. finalOrderorder)throwsException16. wrapInTransaction(newCommand()17. publicvoidexecute()18. add(order,userKeyBasedOn(userName);19. addLineItemsFrom(cart,order.getOrderKey();20. 21. );22. 在列表4中,我提取了一部分通用的代码到wrapInTransaction()(这个样式你可能认识-这是最简单的Spring事务模版的版本)方法中,传递一个命令对象作为工作单元。addOrderFrom()方法包含了一个匿名内部类的创建,这个类以命令模式封装了两个工作单元。封装行为纯粹是Java的设计产物,我需要用到一个不包含任何形式的,独立行为的命令类。Java中所有的行为都必须驻留在一个类中。甚至语言的设计者很早的就看到了这个不足,但是显然在发布后再去考虑不将行为联接到类上就有些晚了。因此在JDK1.1中纠正了这个缺陷,通过添加匿名内部类的方式来实现。这只是以一种语法糖的方式来为少量的方法创建一大堆小类,这样做仅仅是从纯功能角度出发,而非从结构上。如果想看有关Java这方面有趣的文章,请看Steve Yegges的Execution in the Kingdom of Nouns。尽管我非常想要类里面的这个方法,但Java还是强制我去创建一个命令类的实例。这个类本身没有任何用处:它没有字段,没有构造器(这个由java自动生成),并且也没有状态。它纯粹的目的就是为了在方法里包装行为。在函数式语言里,我们通过高阶函数来取代这个模式。如果我不准备用Java的类,那么我可能采用最接近的语义是函数式编程里面的闭包。列表5显示了重构后的例子,但是使用Groovy代替了Java。列表5, 使用Groovy的闭包代替命令类1. defwrapInTransaction(command)2. setupDataInfrastructure()3. try4. command()5. completeTransaction()6. catch(Exceptionex)7. rollbackTransaction()8. throwex9. finally10. cleanUp()11. 12. 13. 14. defaddOrderFrom(cart,userName,order)15. wrapInTransaction16. addorder,userKeyBasedOn(userName)17. addLineItemsFromcart,order.getOrderKey()18. 19. 在Groovy里面,任何位于大括号之间的东西都是一个代码块,并且代码块可以被当作参数来模仿一个高阶函数。在这种情景下,Groovy为你实现了命令模式。Groovy中的每一个闭包块就是一个Groovy的闭包类型,它包含一个call()方法。当你把一对空括号放到变量后面用于保存闭包实例时,该方法会被自动调用。Groovy启用了一些类函数式编程的行为,通过在语言本身使用相应的语法糖来构建适当的数据结构。正如我将会逐步展示的那样,Groovy也包含其他函数式语言的能力。我将在下面的部分继续对闭包和高阶函数做一些有意思的比较。第一级函数函数被认为是函数式语言里面的一等公民,这就意味着函数可以出现在任何地方,正如其他语言的构造体(如变量)那样。在思考不同解决方案的时候,第一级函数的存在允许函数以一种特别的方式来使用,如应用同样的比较操作到相同的数据结构上。这就体现了函数式语言的一个基本思考原则:关注结果,而不是过程。在命令式的编程语言里,我必须考虑算法的每一个原子操作。如列表1的代码显示的那样。为了实现数字归类器,我不得不精确的识别如何去采集整除因子,这就意为着为了确定一个因子,我不得不写代码去遍历所有数字。但是像遍历列表,然后对每一个元素实施操作,这听起来像是很通用的东西。考虑使用Functional Java框架来重新实现数字归类器的代码,代码如列表6所示:列表6. 函数式的数字归类器1. publicclassFNumberClassifier2. 3. publicbooleanisFactor(intnumber,intpotential_factor)4. returnnumber%potential_factor=0;5. 6. 7. publicListfactors(finalintnumber)8. returnrange(1,number+1).filter(newF()9. publicBooleanf(finalIntegeri)10. returnnumber%i=0;11. 12. );13. 14. 15. publicintsum(Listfactors)16. returnfactors.foldLeft(fj.function.Integers.add,0);17. 18. 19. publicbooleanisPerfect(intnumber)20. returnsum(factors(number)-number=number;21. 22. 23. publicbooleanisAbundant(intnumber)24. returnsum(factors(number)-numbernumber;25. 26. 27. publicbooleanisDeficiend(intnumber)28. returnsum(factors(number)-numbernumber;29. 30. 列表6和列表2的不同在于两个方法:sum()和factors()。在Functional Java里, Sum()方法具有List类的foldLeft()方法优势。列表操作概念上的一个具体变化就是被称之为catamorphism,它是列表折叠上的一般化。在这里“向左折叠”的意思是:1.携带一个初始值并组合它到列表的第一个元素上2.携带结果并应用相同的操作到下一个元素上3.一直操作直到列表结束注意当你对一堆数求和的时候,所做的事情是非常明显的:从零开始,加上第一关元素,携带结果去加第二个,重复这个过程直到所有列表的元素都被处理。Functional Java提供高阶函数(在这个例子里就是Intergers.add枚举器)并小心翼翼的为你的代码启用它。(当然Java真的没有高阶函数,但是你可以通过限制具体的数据结构
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年山东警察学院军训动员大会校长发言稿9000字
- 健身销售培训理论知识书课件
- 俄罗斯碳中和课件
- 伤寒护理课件教学
- 2025年江苏省淮安市涟水中学物理高三第一学期期末学业水平测试试题
- 顺义区驾校管理办法
- 资金管理办法使用条件
- 企业百万员工安全培训app课件
- 企业火灾安全培训小常识课件
- 2025年麻醉科镇痛管理规范测验答案及解析
- 眼损伤法医学鉴定
- 空气栓塞演练脚本
- GB/T 37232-2018印刷文件鉴定技术规范
- GB/T 28461-2012碳纤维预浸料
- 酒店服务心理学培训教材课件
- 学前教育史全套课件
- 高一新生入学调查表
- 部编人教版历史七年级上册全册教学课件
- 人教版部编四年级道德与法治上册全册课件
- 《高等数学》全册教案教学设计
- 血栓弹力图-PPT课件
评论
0/150
提交评论