实用函数编程cf_第1页
实用函数编程cf_第2页
实用函数编程cf_第3页
实用函数编程cf_第4页
实用函数编程cf_第5页
已阅读5页,还剩28页未读 继续免费阅读

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第部分学习函数式思维方式读者之所以拿起这本书,可能有各种不同的原因。也许读者是在阅读有关 LINQ 和C# 3.0 或者其他已经深受函数式编程影响的技术时,听说了函数式编程,想知道它还有没有其他值得关注的。读者可能已经听说了:函数式编程可以比较容易地编写并行程序或异步程序。可能还听说了函数式风格的其他一些重要应用 例如,如何编写没有易变状态的程序?也可能是听说 Visual Studio 2010 中添加了一种叫 F#的新语言,希望了解一下这种新语言有何能耐 。在任何一种情况下,关于函数式编程,首先了解到的就是:它所基于的原则不同于过去所 的原则。但这并非意味着将会抛弃所有已经获得的知识,因为

2、基 于.NET 的函数式编程可以很好地与面象式风格和现有库一同协作。虽然基础不同, 但可以在于它们之上进行构建,返回熟悉的领域,从一种不同的视角来观察它们。在第部分, 主要关注这些基本原理。在第 1 章, 研究其中一些原理的实际效果,但不会过于深入。还将编写的第一个 F#程序,使读者在阅读本书的同时开始自己体验 F#。在第 2 章中, 将更为系统地研究函数式编程背后的,并它们如何改变的编程方式。在该章中,大多数示例代码是使用C#编写的,因为许多函数式也可以在 C#中使用。编注: 2011 年 8 月 4 日, Tiobe了的 8 月编程语言榜。Top 20。中最引人注-目的便是微软的 F#,它

3、作为一种新的函数式编程语言首次闯入大学计算机教授 Robert Harper 宣称面象编程已死,认为它是反模块化的、反并行的,而现代计算的根本则是模块化和并行。在这样的大环境下, F#的优势不言而喻。第 3 章将更深入地探究 F#,了解值、函数、和几种内置数据类型。还将在 C#中实现相应的类型和函数。这是用来解释 C#开发如何使用 F#的最简单方式, 在本章后面的现实 C#示例中, 还可以重复利用这些类型。在第 4 章,使用已经学习到的内容, 以 F#开发一个图表应用程序。第 1 章不同的思维方式函数式语言富有表现能力,可以使用简短、紧凑而易读的代码来完成出色的工作。所有这些之所以成为可能,

4、是因为函数式语言为表达抽象概念提供了更丰富的方式。可以隐藏这些代码是如何执行的,而只指定所期望的结果。那些用于指明如何实现这些结果的代码只需要编写一次。由于这些丰富的抽象功能,所有复杂性都隐藏在库中。可以将这种不同的编程方法对于现实应用有着深远的内涵。这种逻辑表达方式使程序更易读,更易于了解其思路,从而更易于理解和改变先前未知的代码。函数式程序易于测试和重构。然而,尽管有这些好处,但到目前为止,函数式语言在很大程度上仍然被主流开发所忽视。今天, 正着新的。需要编写一些程序来处理大型数据集,并能够扩张到大量处理器上。需要应对更大型的系统, 所有须更好地处理其复杂性。这些趋势开启了通向函数式语言,

5、 但它们绝不是使用函数式编程的唯一原因。基于上述原因,现在有许多主流语言包含一些函数式特征。在.NET 世界里,C# 2.0中的泛型深受函数式语言的影响。函数式语言的最基本特征之一就是能够在运行过本章内容理解函数式编程用函数式提高生产效率编写高效、易读的代码实现自己的第一个 F#应用程序创建函数值,而不需要事先它们。而这正是 C# 2.0 利用方法为提供的功能,而 C#3.0 中通过 lambda 表达式使其变得更为容易。整个 LINQ 项目就是植根于函数式编程的。在主流语言正在迎头赶上的同时, 真正的函数式语言也正在受到的一个例子就是 F#,在 Visual Studio 2010 中,它将

6、成为一种持的 Visual Studio 语言。由于公共语言运行时(CLR)的存在,.NET关注。最重要的、受到全面支上的函数式语言演进也在很大程度上成为可能,公共运行时可以使开发实现以下目标:在开发单一.NET 应用程序时,混合多种语言从 F#之类的新语言中丰富的.NET 库由于可以在所有.NET 语言之间共享库,这些新语言要容易得多, 因为在职业生涯中积累的所有知识仍然能够在函数式语言的新环境中使用。在本书中,探索最重要的函数式编程概念, 使用来自.NET 的实例来演示它们。首先描述这些, 然后转而研究一些内容, 正是由于这些内容, 才有可能以函数式方式开发大型现实世界的.NET 应用程序

7、。在本书中将使用 F#和 C# 3.0两种语言, 因为这些中有许多都可以直接应用于 C#编程。当然不必非得使用函数式语言编写代码才能使用函数式概念和模式。但看到 F#代码示例, 可以更深刻地理解到它是如何工作的,利用 F#经常可以更容易地表达解决方案。但已经抢跑了。毕竟本书是关于“ 函数式编程” 的。首先描述一下这个术语的含义,是不是更有意义呢?函数式编程1.1要为函数式编程找出一个精确定义, 是一件非常的事情。存在各种各样的函数式语言, 但并没有一个明确的特征集是所有函数式语言都必须具备的。虽然如此,函数式语言仍有一些共同的属性, 支持多少有些不同的表达方式, 用来表达编程问题的解决方案。将

8、函数式编程同最常见的替代选项命令式编程进行对比,是描述函数式编程的最容易方法。函数式语言函数式编程是一种编程方式, 它强调对表达式的求值, 而不是命令的执行。 这些语言中的表达式是通过函数合并基本值而形成的。Hutton ed. 2002这一定义源于一个学术邮件列表的 FAQ(这个列表是关于函数式语言的),所以它听起来可能有点抽象。请相信, 它的含义很快就会变得清晰。第一个句子中的“ 表达式的求值” 表示函数式方法, 与命令式代码中令的执行” 风格形成对比。命令式语言中一下这两个选项。令被为“ 语句”, 所以使用这一术语。让仔细看语句的执行这种程序被表示为一个命令序列,“ 命令” 也称为“ 语

9、句”。命令指明如何通过创建和使用对象来获得最终结果。在使用这一方法时,通常处理能够被改变的对象,代码中描述了为获得期望结果需要执行哪些修改。例如,首先冲一杯黑咖啡。这个对象是可以更改的, 所以可以添加两块糖来改变它, 以获得期望结果。表达式的求值在函数式编程方式中, 程序代码是一个表达式, 它指明希望作为结果获取的对象属性。没有指明为构造该对象所需要的步骤, 而且不能在创建此对象之前就使用它。例如, 假定希望要一杯加了两块糖的咖啡。加糖了。在加糖之前是不能饮用它的, 但其实当拿起时, 其中已经听起来, 这似乎只有很微小的区别, 但给代码设计方式带来了巨大的改变。规则只有一条:代码编写为表达式,

10、 而不是语句序列, 但这种方法的逻辑结果却有许多种。人们封装和组合代码的方式不同, 人们使用各种技术来编写可重用代码,人们使用的数据结构更适于表示复杂计算的结果, 如此等等。给出一个函数式编程定义是一回事,还需要理解如何一起使用这些概念。这两个了本书的。当读者读完本书后, 不仅可以理解前面给出的定义, 还会对函数式编程有一个直观的感受。这一点要重要得多, 但遗憾的是, 很难用一两句话把它解释清楚。Luca Bolog于 2009 年在 TechEd关于 F#的伟大时Bolog, 2009, 也用到了关于冲咖啡的类比。这种巧合暗示着, 通过学习函数式编程, 您不仅会学习如何以不同方式考虑编程问题

11、, 还会以不同方式考虑有关下午休息。到目前为止, 所有这一切听起来都显得有些抽象, 但本书原书名中包含“ 现实世界”(Real World)是有充分理由的。函数式编程可以提供很多好处。读者可能已经遇到了下面这样一些情况, 在这些情况下, 可以用函数式编程来解决问题(就像本书的作者一样)。您是否发现,由于隐藏的相关性和细微之处, 很难更改代码后的结果?您是否发现自己一遍又一遍地编写相同的模式, 几乎留不出什么时间来处理问题中真正不同且有意义的部分?您是否发现很难的条件执行?代码的头绪, 担心每条语句是否会按正确的顺序、以正确您是否发现很难表达一些抽象内容, 其中隐藏了代码是如何执行的, 而仅指明

12、试图获得什么结果?您是否在异步控制流中之处?, 发现编写出来的代码和意大利面条颇有类似您是否发现很难将任务分为逻辑独立的部分, 以便能够在多个处理器内核上并行执行它们?您是否发现您的代码在现实世界的行为方式与单元测试时不同?在函数式编程如何提高工作效率之前, 首先简要地一下它的历史, 它的悠久程度会令人感到惊讶的。1.2通向现实世界函数式编程的途径函数式编程是一种范例, 其要早于首批计算机的出现。它的历史可以追溯到 20 世纪 30 年代,当时 Alonzo Church 和 Stephen C. Kleene 介绍了一种称为“ lambda 演算” 的理论, 作为他们对数学基础研究的一个组成

13、部分。尽管这一理论并没有实现他们的初衷, 但仍然在一些逻辑分支中得到了应用, 并演进为一种非常有用的计算理论。为了探究函数式编程的基本原理, 可以在第 2 章中找到有关 lambda 演算的简要介绍。在发明计算机之后, lambda 演算跳出了它的最初领域,成为早期函数式编程语言的灵感。1.2.1函数式语言第一个函数式编程语言在 2008 年庆祝了它的 50 岁生日。LISP,由 John McCarthy于 1958 年创造,直接以 lambda 演算理论为基础。LISP 是一种极为灵活的语言,首先倡导了许多今天仍在使用的编程设定。, 包括数据结构、收集和动态类型在 20 世纪 70 年代,

14、 Robin Milner 开发了一种名为 ML 的语言。它是 F#所属语言系列中的第一个语言。受类型化 lambda 演算的启发, 它添加了类型的概念, 甚至还允许人们像现在处理.NET 泛型那样编写“泛型” 函数。ML 还具有一种功能强大的类型推理机制, 它对于现在用 F#编写简洁程序实用扩展, 出现于 1996 年。它是最早将面。OCaml 是对 ML 语言的象式方法和函数式方法结合在一起的语言之一。OCaml 是对 F#的一个重要启发,它必须将这些范例结合起来,才能成为一流的.NET 语言, 并且还是真正的函数式语言。其他的重要函数式语言包括 Haskell(这一语言的数学纯正度和雅致

15、程度令人惊叹)和Erlang(这一语言因为其并行消息传递特色而闻名,在第 16 章中进行)。在专门Haskell 和 LISP 相对于 F#的优势时,会学习有关这些语言的来看一看 F#的历史,作为本部分内容的结束。内容,但现在让上的函数式编程1.2.2.NET.NET 的第一个版本发布于 2002 年, F#语言的历史可以追溯到同一年份。F#在开始时是Syme 及其同事从事的一个微软研究项目, 其目标是在.NET 中加入函数式编程。一般来说, F#和类型化函数式编程增加了对.NET 中泛型的需求分量, F#的设计深入参与了.NET 2.0 和 C# 2.0 泛型的设计与实现。有了在框架中实现的

16、泛型,F#开始更快速地发展,F#中所使用的编程风格也开始发生变化。在开始时, 它作为一种支持对象的函数式语言, 但作为一种成熟语言, 更自然的做法似乎是取两种风格之中的最佳者。因此, F#现在被更准确地描述为一种多范例语言, 它将函数式方法和面象式方法结合在一起, 还带有一大堆工具,使它能够以交互方式进行编写。F#F#是一种针对.NET 框架的函数式编程语言。它将函数式编程的简洁性、表现性和组合性风格与.NET 的运行库、库、互操作性和对象模型结合在一起。F#首页从其早期开始, F#就已经是.NET 的第一类成员。它不仅能够所有标准的.NET组件, 而且任意其他.NET 语言也可以 用 F#开

17、发的代码(这一点同样重要)。这样就有可能使用 F#来开发独立的.NET 应用程序,也可以开发大型项目的一部分。 F#一直伴有 Visual Studio 中的支持,在 2007 年,开始将 F#从一种研究项目转换为一种具有完全生产品质的语言。2008 年, 微软宣布 F#将成为随 Visual Studio 2010发布的语言之一。单就这一点, 就有充分的理由来关注 F#和整个函数式范例了,但现在让看一看更注重实效的理由。1.3用函数式编程提高生产效率许多人都发现函数式编程非常雅致, 甚至是非常美丽的, 但这个理由并以在商业环境中使用这种方法。雅致并非关键。以函数式风格进行编码的关键原因在于它

18、能够使您和您的团队具有更高的生产效率。在本节,研究函数式编程为人们提供的关键好处, 并解释它是如何解决现代开发中一些最重要问题的。在研究具体好处之前,下。函数式编程不是一种严格限定的技术, 因为函数式先从总体角度探讨一能够以不同形式出现。1.3.1函数式范例函数式编程是一种编程范例, 这意味着它定义了在思考问题时可以使用的概念。但它并不精确应当如何在编程语言中表现这种概念。因此, 存在许多种函数式语言, 每一种都强调函数式的不同特征和方面。可以拿一种非常熟悉的范例进行类比: 面象编程(OOP)。在面象方式中,用对象来思考问题。每个面象的语言都有关于对象的概念, 但在不同语言中, 其细节是不一样

19、的。例如, C+具有多重继承, 而 JavaScript 具有原型。此外,还可以在一个不是面象的语言中(例如 C 语言)使用一种面象的方式。这样并不是非常舒服, 但仍然可以享受其中一些好处。编程范例并不是互相排斥的。C#语言主体是面象的, 但在 3.0 版本中,它支持几种函数式特征, 所以可以直接使用来自函数式风格的技巧。另一方面, F#主体是一种函数式语言, 但它完全支持.NET 对象模型。组合范例的绝妙之处在于,可以选择最适合解决问题的方法。即使您没有打算使用函数式语言, 学习这种函数式范例也是值得的。通过学习函数式风格, 可以学到一些概念, 而利用这些概念可以更容易地思考和解决日常的编程

20、问题。有趣的是, 许多标准的面的函数式概念进行编码。象风格都描述了如何以 OOP 风格对一些明确下面让来关注函数式编程的好处。首先看一下性编程风格, 它为提供了丰富的词汇,的目的。性编程风格1.3.2在性编程风格中,表达程序的逻辑, 而不指明执行细节。这一描述听起来有些熟悉, 因为它非常类似于在 1.1 节看到的函数式编程定义。但性编程是一种更具一般性的, 可以使用不同技术加以实现。函数式编程只是实现这一的能的。式。让来演示一下, 函数式语言是如何使编写性代码成为可在编写一个程序时,须使用计算机能理解的词汇向计算机解释的目的。在命令式语言中, 程序由命令组成。可以添加新令, 例如,“ 显示客户

21、详细信息”, 但整个程序是各个步骤的描述, 表明计算机可以如何完成整个任务。一个程序示例是:“ 获取列表中的下一位客户。如果此客户详细信息。如果列表中还有客户, 则返回开头。”英国, 则显示他的注一旦程序发展后, 词汇中令数目会变得非常大, 使词汇的使用变得。在这种情况下, 面象编程就有用武之地了, 因为它允许以一种更佳方式来组织这些命令。可以将涉及客户的所有命令与某一客户实体(一个类)相关联, 这样就可以使描述变得明了。其程序仍然是一个命令序列, 指明它会如何进行。函数式编程提供了一种完全不同的词汇扩展方式。并不仅限于添加新的原始命令, 还可以添加控制结构一些基元, 可以如何将命令组合在一起

22、,以创建一个程序。在命令性语言中,可以在一个序列中或使用有限个内置构造(例如循环)来组合命令, 但如果查看典型的程序,将会看到许多递归结构常见令组合方式。事实上, 这些递归结构中有一些是人们非常熟悉的, 由“ 设计模式” 描述。但在命令性语言中, 需要一遍又一遍地重复键入相同的代码结构。在的例子中, 可以看到一种模式, 它可以表达为“ 对于 第二个命令 返回真值的所有客户, 运行 第一个命令 ”。使用这一基元, 可以将的程序表达为“ 对于每一位在英国生活的客户, 显示其详细信息”。在这个句子中,“ 在英国生活” 指定了第二个参数,而“显示其详细信息” 表示第一个参数。让对比一下同一问题的两个句

23、子。获取列表中的下一位客户。如果此客户英国, 则显示他的详细信息。如果列表中还有客户,则返回开头。对于每一位在英国生活的客户, 显示其详细信息。就像前面关于冲咖啡的类比一样, 第一个句子描述了到底如何实现的目标, 而第二个句子则描述了希望实现什么。提示这是命令式与式编程风格的基本不同。当然, 您还会同意第二个句子的可读性要强得多,而且更好地反映了“ 程序” 的目标。到目前为止,已经使用了一个类比, 但在本章后面还将看到这一是如何映射到实际源代码中的。这并非函数式编唯一能够使生活变得更轻松的方面。在下一节,会看到另一个概念, 利用这个概念, 就可以很轻松地理解程序所做的工作了。理解程序所做的工作

24、1.3.3在通常令式风格中, 程序由对象组成, 对象拥有状态, 既可以直接改变,也可以通过调用该对象的某一方法来完成。这意味着在调用一个方法时, 很难判断哪个状态受到了该操作的影响。例如,在代码1.1 中的 C#代码段中, 创建了一个椭圆,获取其边框, 然后针对返回的矩形调用一个方法。最终将此椭圆返回给该方法的调用者。Ellipse ellipse = new Ellipse(new Rectangle(0, 0, 100, 100);RectangoundingBox = ellipse.BoundingBox;boundingBox.Inflate(10, 10);return ellip

25、se;如果仅查看此代码, 如何知道在运行此代码之后, Elipse 的状态是怎样的呢?这是很难的,因为 boundingBox 可能是对该椭圆边框的一个,而 Inflate可能同时修改此矩形,改变椭圆。或者, Rectangle 类型可能是一个值类型(使用 C#中的 struct 关键字),在将其指定给一个变量时,它被。Inflate 方法可能并没有修改矩形,而是返回一个新的矩形作为结果,所以第三行是没有效果的。在函数式编, 大多数数据结构都是不可变的,这就意味着不能修改它们。一旦创建了 Ellipse 或 Rectangle,就不能再改变它。唯一能做的事情就是创建一个带有新边框的新 Elli

26、pse。这样就可以很轻松地理解程序所做的工作了。如代码1.2 所示,如果 Ellipse 和 Rectangle 是不可变的,面将会看到,可以很轻松地理解这个程序的行为了。可以重写前面的代码段。下Ellipse ellipse = new Ellipse(new Rectangle(0,RectangoundingBox = ellipse.BoundingBox; Rectangle smallerBox = boundingBox.Inflate(10,0, 100, 100);10); return new Ellipse(smallerBox);当使用不可变类型编写程序时,一个方法唯一

27、能做的就是返回结果它不能修改任务对象的状态。可以看到, Inflate 返回一个新的矩形作为结果, 并构造一个新椭圆,以返回一个边框经过修改的椭圆。在第一次看到这一方法时,可能感到不,但请记住,对于.NET 开发来说, 这并不是一种新。String 可能是.NET 世界中最著名的不可变类型,但还有许多例子,如 DateTime 和其他值类型。代码 处理不可变椭圆和矩形 ) 代码 处理椭圆和矩形 ) 函数式编程在这一上走得更远了一些, 可以更容易地了解一个程序所做的工作, 因为一个方法的结果完整地指明了该方法所做的工作。后面还将更详细地不可变性, 但首先来有用。多线程应用程序的实现, 不可变性对

28、于这个领域来说极为1.3.4并发友好的应用编程使用传统令式风格编写多线程应用程序时,要面对两个问题。很难将已有串行代码转变为并行代码, 因为必须将很大一部分代码库修改为显式使用线程。共享状态与锁的应用非常。编程必须仔细考虑如何使用锁来避免争用条件和死锁,还要为并行执行留出足够的空间。函数式编程为提供了解决方法。在使用性编程风格时, 可以向已有代码中引入并行机制。可以将一些规定如何合并命令的基元替换为并行执行命令的版本。有了不可变性,不可能引入争用条件, 可以编写无锁代码。马上就可以看出程序的哪些部分是独立的,可以修改程序, 以并行运行这些任务。这两个方面影响了设计应用程序的方式, 因此能够非常

29、容易地编写并行执行的代码, 充分利用多核机器的。编写不可变代码并不意味着能够免费实现并行化, 其中还需要一些工作, 但函数式编程可以将实现并行化所需要的额外工作量降至最低。不仅如此, 当开始函数式思维后, 还会看到设计中的其他变化。1.3.5函数式风格如何塑造代码函数式编程范例无疑会影响人们设计与实施应用程序的方式。这并不意味着必须从头开始, 因为今天正在使用的许多编程原则同样适用于函数式应用程序, 尤其是在设计层次, 设计应用程序的结构时,更是如此。对于在实现层级解决问题的方法, 函数式编程可能会迥然不同。在学习如何使用函数式编程时,不必一开始就完全采用新模式。在 C#中, 学习了如何高效地

30、使用这些新功能。在 F#中,可以经常使用 C#构建直接对等的构造,同时仍然可以获得实践体验。当您成为更有经验的函数式开发简洁的方式来表达自己的想法。之后,将会学习更高效、更下面的实施层级。总结了函数式编程是如何影响您的编程风格的,从设计层级一直到实际上的函数式程序仍然以面象式设计作为设计应用程序与组件结.NET构的杰出方法。大量的类型和类被设计为不可变的,但仍然有可能创建标准类,尤其是在与其他.NET 库协作时。有了函数式编程,可以简化许多标准的面象式设计模式,因为这些模式中有一些与 F#或 C# 3.0 中的语言特征相对应。另外,还有一些设计模式,在以函数式实现代码时是不需要的。在本书中会看

31、到许多这样的例子, 尤其是在第7 章和第 8 章中。函数式编程的最大影响是在最低层次,也就是对应用程序的算法和行为进行编码时。由于将性风格、简洁的语法和类型推理结合在一起, 所以函数式语言可以帮助在本书后面以一种易读方式,简洁地表达算法。所有这些方面。首先用于实现方法和函数的函数式值(functional value),然后再进一步,来研究设计与体系结构。读者会发现函数式编程特有的新模式,还会了解到自己已经熟悉的面域,或者已经不再需要这些模式。象模式是否适用于函数式领接下来将要介绍什么到目前为止,仅了一般意义上的函数式编程。读者已经看到函数式编程是如何允许开发在编程时扩充词汇的, 以及它是如何

32、使代码更具性的。还了不可变数据结构以及它们对程序的意义。在下一节,研究这两个基本概念的四个实用内容(参见下图)。性编程和不可变的数据结构从整体上影响了代码的可读性和清晰性,在 1.4.1节和 1.4.2 节将会看到两个例子。然后,研究一个通用问题并行性,并到目前为止,习, 但是没关注的是什么内容使函数式编程与众不同, 以及它为什么值得学比实际代码更能说明问题。在下一节,会看到在补充内容中提到的四个例子的源代码。1.4函数式编程举例下面这些例子的目标是向读者表明, 函数式编程绝不是一种理论规则。读者将会发现, 自己可能已经在已有.NET 技术中使用了函数式。阅读有关函数式编程的资料将会帮助读者更

33、深入地理解这些技术, 并且更为高效地使用它们。在本书后面部分还会看到一些例子, 它们都显示了函数式风格重要的实用好处。在第一组例子中,研究性编程。使用性风格表明目的1.4.1前文介绍了性编码风格如何帮助您提高生产效率。支持性风格的编程语言允许人们增加一些新的方式, 用于组合基本结构。在使用这种风格时, 并不限于基本的语句序列或内置循环, 所以得出的代码地是描述计算机应当做什么, 而不查看性编程如何帮助实现代码的并行化, 以及不可变数据结构如何使进程更安全。是如何来做。概括性地来这一风格, 因为这种是通用的,并不是与特定技术绑定在一起的。但最好还是使用一些大家可能已经了解的例子来进行演示,以说明

34、如何在特定技术中应用这一。两个例子中, 研究 LINQ 和 XAML 的声明性风格。如果您不了解这些技术,请不用担心这些例子非常简单,不需要任何背景知识就足以掌握它们。事实上,代码易于理解(甚至是在一种不太熟悉的上下文环境中),正是性风格的主要好处之一。1. 在 LINQ 中处理数据如果您已经使用过 LINQ,那这个例子只不过就是一个提醒。但用它来说明一些更为重要的内容。在代码数据。1.3 中,将使用标准令式编程风格来处理IEnumerable GetExpenisveProducts() List filteredInfos = new List(); foreach(Product pro

35、duct in Products) if (product.UnitPrice 75.0M) filteredInfos.Add(String.Format(0 - $1,product.ProductName, product.UnitPrice); return filteredInfos;可以看出, 这一代码由若干基本命令组成。第一个语句创建一个新列表,第二个语句对所有产品进行迭代,后面的一个向列表中添加一个元素。但希望能够在更次上描述这一问题。用更抽象的术语来说,该代码对一个集合进行筛选,并返回有关所有已返回产品的信息。在 C# 3.0 中,可以使用查询表达式语法来编写相同代码。代码1

36、.4 中给出的这个版本,更接近的真实目标它使用相同的来筛选和转换数据。代码 性数据处理 ) IEnumerable GetExpenisveProducts() 代码 命令式数据处理 ) return from product in Productswhere product.UnitPrice 75.0Mselect String.Format(0 - $1,product.ProductName, product.UnitPrice);用来计算结果(filteredInfos)的表达式是由基本运算符(如where 或 select)组合而成的。这些运算符以其他两个表达式为参数,因为它们需要

37、知道希望筛选或选择什么作为结果。使用前面的类比, 这些运算符为提供了一种新的方法, 可以将代码片段组合起来表明的目的, 而所需编写的代码较少。注意,代码1.4中的整个计算都被编写为描述结果的单一表达式,而不是构造它的一系列语句。在本书中将会秉持这种风格。在诸如 F#等是一个表达式。性更强的语言中,编写的所有内容都这个代码中另一个有趣的内容是,解决方案中的许多技术细节都被移到基本运算符的实现中了。这样使代码更为简单, 而且也更灵活, 因为可以改变这些运算符的实现,而不需要对使用这些运算符的代码进行较大改变。后面将会看到, 通过这种方式,再来实现数据处理代码的并行化,就要容易多了。LINQ 不是唯

38、一一种依赖于性编程的主流.NET 技术。现在让注意力转移到 Windows Presenion Foundation 和 XAML 语言上来。2. 在 XAML 中描述用户界面Presenion Foundation (WPF)是一种用于创建用户界面的.NET 库。这个性编程风格。它将描述 UI 的部分从实现命令性程序逻辑的部分分离开Windows库支持来。但创建。WPF 中的最佳实践是使程序逻辑最小化,并尽可能以性方式进行性描述表示为一个树状结构,它是由代表各个 GUI 元素的对象生成的。可以在 C#中创建它, 但 WPF 还提供了一种更为舒适的方法,这种方法用到了一种以XML 为基础的语言

39、XAML。代码1.5 对比了两种代码: 一种是以 XAML编写的代码,一种是使用命令式 Windows Forms 库实现相同功能的代码。代码 使用性和命令性风格创建 ;/ 32 和 ) / 使用 Windows Forms令性用户界面protected override voie.Graphics.FillRectangPa(PaEventArgs e) rushes.Black, CntRectangle);e.Graphics.FillEllipse(Brushes.LightGreen, 0, 0, 75, 75);很难确定是什么使第一个代码段更具性。XAML 代码通过组合基元并指定其

40、属性来描述 UI。整个代码是单个表达式,它创建了一个包含绿色椭圆的黑色画布。命令式版本指明了如何绘制该 UI。它是一个语句序列,指明应当执行什么操作来获得所需要的 GUI。性风格表达的是“ 内容”, 而命令性风格说明的是“ 方式”,这个例子演示了这两者之间的差别。在性版本中, 不需要有关基础技术细节的太多知识。如果要研究该代码,不需要知道 WPF 是如何表示和绘制这个 GUI 的。在查看 Windows Forms 示例时,在代码中可以看出所有的技术细节(例如,刷子的表示,绘制的顺序)。在代码1.5 中, XAML 和这个绘制代码之间的对应性是清楚的,但可以结合使用XAML 和 WPF,来描述

41、此程序更为复杂的运行时内容。现在看一个例子:这一个表达式生成了一个动画,在 5 秒内,将椭圆(由名称 greenEllipse 指定)的Left 属性从数值 0 改变到数值 100。此代码是使用 XAML 实施的,但过显示构造对象树,用 C#语言来编写。DoubleAnimation 是一个类, 所以也可以通指定其属性。XAML 语言增加了编写技术规范的更具性的语法。在任一情况下, 令式编程时,实现一个一个事件处理程序, 每过几由于 WPF 的本质,此代码都是性的。当采用传统动画的代码是相当复杂的。必须创建一个定时器, 微秒就会调用它,然后再计算椭圆的新位置。.NET 中的性编程到目前为止,读

42、者已经看到了几种基于性风格的技术,并了解了它们如何使问题更容易得到解决。读者可能会问自己, 怎样利用它来解决自己呢。在下一节,简要看一个来自第 15 章的例子,这个例子向演示了这一点。性函数式动画3.函数式编程允许用户以性风格编写自己用于解决问题的库。您已经看到 LINQ是如何用于数据处理的,看到 WPF 是如何用于 UI 的,但在函数式编,经常针对自己创建库。前面曾经提到,利用性风格有可能忽略一些实施细节,当时省略了一些内容。函数式编程并没有任何神秘的力量,可以替实现其中的部分。需要在设计自己的库时,实现所有的技术细节。但这些实现细节可以隐藏在库中( 就像LINQ 向隐藏了所有复杂性一样),

43、所以可以一劳永逸地解决一般性问题。代码1.6 使用一个性库来创建在第 15 章中开发的动画。读者不需要完全理解这些代码就能看出使用性风格的好处。它描述了动画看起来应当是什么样的,而不是如何使用定时器来绘制它,在这一点上,它与 WPF 类似。var greenCircle = Anims.Circle( Time.Forever(Brushes.OliveDrab), 100.0f.Forever();var blueCircle = Anims.Circle(Time.Forever(Brushes.SteelBlue), 100.0f.Forever();var movingPo= Time

44、.Wiggle * 100.0f.Forever(); var greenMoving = greenCircle.Translate(movingPo, 0.0f.Forever(); 代码 创建函数式动画 ) WPF 和 LINQ 是使用 性风格的两种主流技术,但还有许多其他技术可供使用。 LINQ 的目标是简化通用语言中的数据处理。它从许多使用 性风格的数据处理语言中抽取 ,所以可以在 SQL 或 XSLT 之类的语言中找到 性方法。在 C#或 VB.NET 中使用 性风格的另一个场合是在使用.NET 属性时。属性为我们提供了法,用来注释类或其成员,并指定可以如何在特定场景下使用它们,例

45、如在设计器中编辑一个 GUI 控件。这是 性的,因为 在处理控件时指定了希望从设计器中得到什么,而没有编写代码以交互式配置设计器。var blueMoving = blueCircle.Translate(0.0f.Forever(), movingPo);var animation =e(greenMoving, blueMoving); 在第 15 章详细解释所有内容。读者大概可以猜到, 这个动画创建了两个椭圆: 一个绿色的, 一个蓝色的。然后, 它使用 Translate方法移动两个椭圆的位置, 然后使用 Come 方法将它们组合到一个动画中(表示为 animation 值)。如果这个动

46、画呈现在一个窗体中, 将会得到如图 1.1 所示的结果。图 1.1 右边的椭圆正由左向右移动, 而左边的椭圆正由上向下移动整个性描述都是以动画值为基础的。其中有一个名为 Time.Wiggle的基元动画值,它的取值在-1 与 1 之间变动。另一个基元构造 x.Forever()创建了一个动画值, 它的取值范围是-100 至100。这些动画值可用于指定图形对象(如这两个椭圆)的动画。在图 1.1 所示状态中, 绿色椭圆的 X 坐标和蓝色椭圆的 Y 坐标,都接近于+100。在代码1.6 中,不需要了解有关动画值表示方式的所有内容,因为通过计算基元动画值来描述动画。在此代码中还可以看到性风格的另一个

47、方面,那就是这个动画在原理上是使用单一表达式描述的。通过几个局部变量, 可以增加其可读性, 但如果用变量的初始化代码来替换该变量, 这个动画仍然是相同的。组合能力性库的一个重要特征就是能够以组合方式使用它们。从代码1.6 中所示的文中, 方法。代码研究了性编程,当以函数式语言编程时,大多都使用这种1.6 说明如何在一个高级库中使用这一风格来描述动画。在下一节,注意力转移到一个技术性更强,也更有意义的函数式特征:不可变性。理解使用不可变性的代码1.4.2面介绍函数性风格的好处时, 了不可变性。以一个椭圆的边框为例进行了说明,当时并不清楚该代码的行为方式。一旦写该代码, 理解起来就容易多了。后文将

48、更详细地是展示不可变对象在实践中是怎么样的。使用不可变对象重新编。这个例子的目的这一再次说明, 如果现在未能详细了解所有内容, 请不要着急。设想正在编写一个代码, 其中有一些人物是的目标。可以用类来表示人物,下面给出这个类的一部分(代码1.7)。代码 人物的不可变表示 ) classrea reaGameCharacter 将所有字段为只读的lyly Pohealth;location;public GameCharacter(this.health = health;health, Polocation) 一劳永逸地初始化不可变字段this.location = location;publi

49、c GameCharacter HitByShooting(Po newHealth = CalculateHealth() 返回健康程度经过更新的人物);return new GameCharacter(newHealth, this.location);public bool IsAlive get return health 0; 动画库中可以看出这一点。 可以由少数几个基元( 例如 Time.Wiggle 和 x.Forever()生成大量动画值。与此类似,通过向简单的动画图形对象实施操作(如 Translate 或e),就可以组合动画。另一个例子是,在 LINQ 中,可以将一个复杂查

50、询的一部分移到分离的查询中, 并重复使用它。 可以构造自己的基元(例如用于创建轨迹圆),并用它们来构建 的动画(如一个 系)。/其他方法及属性略在 C#中, 就意味着可以使用 realy 关键字,明确地将一个字段标记为不可变的。这不能改变此字段的值,但如果这个字段是一个不可变类的,那么仍然可以修改目标对象。在创建一个真正不可变类时,需要确认所有字段都被标记为 realy,并且这些字段的类型为基元类型、不可变值类型或其他不可变类。根据这些条件, GameCharacter 类是不可变的。它的所有字段都用 realy 修饰符进行了标记。是一个不可变基元类型,Po是一个不可变值类型。当一个字段为只读

51、字段时, 只能在创建该对象时对其进行设置,所以只能在构造函数中设置人物的健康程度和位置。这意味着,一旦对象被初始化之后, 就不能再修改它的状态了。那么,如果一个操作需要修改人物的状态,应该怎么做呢?在查看 HitByShooting 方法时,可以找到。它实现了在中被射击后的反应。它使用 CalculateHealth 方法(示例中没有给出)来计算此人物的新健康程度。在命令性风格中,随后将更新该人物的状态,但这是不可能的,因为这种类型是不可变的。这个方法创建了一个新的 GameCharacter 实例,以表示修改后的人物,并将其作为结果返回。上例中的类表示了一种典型的不可变 C#类设计, 在本书

52、中将一直使用它(仅进行少许修改)。既然读者已经知道不可变类型是什么样的了,让看一些。阅读函数性程序在代码1.1 中已经看到一个使用不可变类型的示例,当时做出结论:它使中使用的代码代码的可读性更强。在本节中, 片段。考虑两个可以在函数式代码1.8 中给出两个例子,各涉及两个人物(player 和 monster)。第一个例子说明可以如何执行怪物 AI,以执行单一步骤,然后测试玩家是否处于中,第二个例子说明如何处理一次枪击。之代码 一个函数式的代码片段 ) var movedMonster = monsterformStep(); var inDanger = player.IsCloseTo(m

53、ovedMonster);(.)var hitMonster = monster.HitByShooting(gunShot); var hitPlayer = player.HitByShooting(gunShot); (.)此代码的第一部分运行怪物 AI 的一个步骤,以移动该怪物,获得该怪物的一个新状态,然后检查玩家是否接近该怪物新计算后的位置。第二部分处理虚拟世界中的一次枪击。该代码生成一个表示更新后怪物的值和一个表示玩家新状态的值。函数式中的所有对象都是不可变的, 所以当针对一个对象调用一个方法时, 不能修改它本身或任意其他对象。如果知道了这一点, 就可以对前述示例进行一些观察。在第

54、一个代码片段中,首先调用怪物的PerformStep 方法。此方法返回一个新怪物,将其指定给一个名为 movedMonster 的变量。在下面一行中,使用这个怪物来查看玩家是否靠近它,以及是否处于中。可以看到,第二行代码依赖于第一行代码。如果改变这两行的顺序,程序将不会编译, 因为 movedMonster 未在第一行中。如果以命令性风格来实施,此方法通常不会返回任何结果,它将只修改 monster 对象的状态。在这种情况下, 可以重新排列代码行,此代码将会编译,但这样会改变程序的含义,程序可能会开始错误行为。第二个代码片段由两行代码组成, 当中发生枪击时, 这些代码行将生成一个新怪物和一个新

55、玩家对象,其健康属性得到了更新。这两行是独立的,意味着我们可以改变其顺序。这一操作能否改变程序的含义呢?看起来是不会的,当所有对象都是不可变时,的确不会改变。令人惊讶的是,如果 gunShot 是可变的,那在命令性版本中是可能改变其含义的。这些对象中的第一个可能会改变枪击的某一属性,其行为取决于这两个语句的顺序。重构与单元测试如您所知, 不可变性有助于理解一个程序做了什么工作,所以在重构代码时会有所帮助。另一种有意义的函数式重构是在执行某一代码时进行改变。该代码可能当函数式编程如何提高的生产效率时,曾经提到, 不可变性是一个非常重要的特征,可以使并行程序的编写过程变得更为容易。在下一节,简要研

56、究这一及其他相关。1.4.3编写高效的并行程序函数式编程可以使并行程序的编写过程变得更为容易,这可能是您拿起本书的原因所在。在本节, 两个例子中, 技术,作为.NET 4.0研究一些例子,用来演示函数式程序的并行化是多么容易。使用 Parallel Extens to .NET,这是来自微软的一项新的一部分发布,用于编写并行应用程序。可以预期, ParallelExtens to .NET 对于函数式代码极为有用。不会深入细节希望表明, 函数式程序的并行化要容易得多, 更重要的是,相对于命令式代码, 其出错几率要低得多。1. 实现不可变程序的并行化首先让再看看代码1.8。已经看到两个代码片段,

57、它们摘自一个以函数式编写的。在第一个代码片段中, 第二行使用了第一行的结果(移动后的怪物状态)。由于使用了不可变类,所以可以看出,它没有给机制的空间。留出任何引入并行第二个代码片段由两个独立的代码行组成。前面,在函数式编,可以并行运行程序的独立部分。现在可以看到,不可变性为提供了一种绝佳方式,以发现程序的哪些部分是独立的。即使不了解任何细节, 也可以看出变化之处, 这在程序第一次到达它时运行, 也可能被推迟, 在需要其结果时再执行。这种推进程序的方式在 F#中非常重要,而不可变性使得 C#中的重构也变得更为容易。在第 11 章重构。还有一种情况也可以表明不可变性是有利的, 那就是在为函数式程序

58、生成单元测试时。在一个不可变世界里,一个方法能做的唯一事情就是返回一个结果,所以只需要测试一个方法是否会为指定参数返回正确的结果。同样, 第 11 章将对这一进行。一变化使这两个操作能够并行执行。对源代码的改变非常小,如下所示:var hitMonster = Task.Factory.StartNew() = monster.HitByShooting(gunShot);var hitPlayer = Task.Factory.StartNew() = player.HitByShooting(gunShot);唯一需要做的就是将计算封装在来自 Parallel Extens 库的 Task

59、 任务中。(我需要编写的代码量较少,而们将在第 14 章详细Future。)其好处并不只是是可以保证此代码是正确的。如果在命令式程序中进行类似修改,那就必须仔细审核 HitByShooting 方法(以及它所调用的所有其他方法),以找出它某一可变状态的所有位置,并添加锁,以保护或修改共享状态的代码。在函数式编程中,一切都是不可变的,所以不需要添加任何锁。本节的例子是一种低级的“ 基于任务的并行机制” 形式, 它是在第14 章中性编程看到的三种方法之一。在下一节, 风格。研究第二种方法,它得益于2. 使用 PLINQ 的性编程风格为性并行机制提供了另外一种用于编写并行程序的技巧。大家知道,使用性

60、风格编写的代码是使用基元组合的。在 LINQ 中,这些基元是查询运算符, 如 where 和 select。在性风格中, 可以很轻松地替换这些基元的实施方式用并行执行的查询运算符替换标准查询运以及 PLINQ 所做的工作:它允许算符。代码那些在代码1.9 显示一个查询,它查询了这个虚拟中的所有怪物,并清除了上一步骤中已经中显示两个版本。的怪物。这一改变极为简单,所以可以在一个var updated =var updated =from monstersfrom monsters.AsParallel() let nm = m.PerformStep()where nm.IsAlive sele

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论