VS2008亮点:用Lambda表达式进行函数式编程
点击次数:88 次 发布日期:2008-11-06 08:13:36 作者:源代码网
|
Linq这种类似于SQL的语言,含有很多通常我们传统语言所不具备的要素。比如我们可以用一条语句将规则作用于整个集合之上,而规则则是通过表达式来传递的。也就是说我们编写这样的语句时,查询中的表达式并不立即执行,而是之后对整个集合的每个元素进行分别调用。这里面一个重要的思想就是我们将运算规则传递给了查询语句,而不是表达式的值或结果。这种思想在函数式语言中尤为重要,我们通过函数为媒介,用组合与递归的方式将运算规则逐渐抽象为一个完整的应用程序。 当然,整个函数式编程范式与我们所熟悉的命令式语言都是不同的,这里我们仅讨论函数式编程的一些思想,希望能够启发思考,或作为命令式编程的一种补充来使用。其实我们在编写Linq代码中已经用到了函数式编程的思想: 软件开发网 www.mscto.com int[] a; a.Where(i => i >0).Select(i => i * 2); 这里,Where函数将我们用Lambda表达式传递的运算规则作用于a[]的每一个元素上,然后返回满足条件的所有元素。这就是一种筛选操作。而Select函数则将规则作用于集合的每一个元素后返回新值组成的集合。这是一种映射操作。 C#简短的Lambda语法为我们编写类似的逻辑提供了很大的方便。不过呢,从下面开始,我将使用Visual Basic 2008的Lambda表达式继续讨论。有人可能奇怪为什么不继续使用C#了呢,下面我就会介绍VB2008编写函数式编程的独特之处。即使您用C#,我仍建议您往下看完,您会看到有些程序用VB来写更容易想清楚;而C#的对应做法下面也会有小小的挑战等着您。 首先我们熟悉一下VB2008的Lambda表达式语法。它由两个部分组成: Function(参数列表)表达式 Function(a) a + 1 "等于C#的 a => a + 1 Function(a As Integer) a + 1 "显式指定类型 Function(a, b) a + b "两个或多个参数 如您所见,和普通的函数声明相比,它只是没有函数的名字,以及不需要使用Return语句而已。注意VB的Lambda表达式只能使用单行表达式作为函数体,而且必须有返回值,C#的Lambda表达式是没有这些限制的。接下来就是重头戏,VB2008的Lambda表达式拥有一些C#3.0所不具备的突出特性,使得它成为我们学习函数式编程的首选: 1.匿名委托特性 VB2008支持一种称作匿名委托的特性。请不要和匿名函数的概念弄混,有些人喜欢把C#的匿名方法教成匿名委托,实际上两者并不相同。VB2008可以根据Lambda表达式的签名,自动生成委托的定义。也就是说在VB中使用Lambda表达式,不用事先声明接受它的委托类型。这使得VB的Lambda表达式更像变量,而不是函数。 Dim f = Function(i) i + 1 无需声明f的类型,它自动从Lambda表达式推导 f(2) 调用,结果为3 而C#则需要显式定义委托的类型,是不能用var f = (int i) => i + 1; 的形式来定义Lambda表达式变量的。后面我们会看到Lambda表达式的类型令人惊奇地复杂,以至于要显式声明有时会过于困难。而且匿名委托带来的好处绝不仅仅只有这一点……。 2.声明同时调用 拜匿名委托所赐,VB的Lambda表达式在任何时候都具有可调用的语义,即使是刚刚声明出来: Dim result = (Function(a, b) a + b)(1, 2) result结果为3 这种即时调用的用法实现某些逻辑的时候提供了极大的方便。而C#的Lambda表达是不具备即时调用的能力,必须用一个已知委托类型的变量接受它,然后才能通过委托变量调用。 3.同签名委托间的类型转换 这是一个VB编译器帮忙实现的小特性,即如果两个委托类型的参数之间有互相兼容的特性(完全一致或可以进行转换),那么两个委托变量之间就可以直接进行类型转换。而且这个转换支持VB的隐式转换规则。后面我们会看到,这个用法对编写动态语义的函数非常有帮助。 Delegate Function A(x As Integer)As Integer Dim x As New A(Function(i) i + 1) Dim y As Func(Of Integer, Integer) y = x 类型转换达成! C#并不支持这种类型转换,但C#实现相同的功能也极为容易,你知道怎样写吗?不妨考虑一下。 以上三点特性加上VB的动态语言特性(隐式类型转换,后期绑定),使得VB的Lambda语法非常接近正牌函数式语言,比如Lisp。当然与现代函数式语言相比,这些功能还显得有些匮乏。下面我们就开始一个有趣的例子:用函数表示数据。Lambda函数的语法能够从表达式所在的上下问中捕获变量,这种称之为“闭包(Closure)”的特性使得函数具有表示数据的能力。例如我们不用Structure或Class之类的语句,就能完全生成一种包含两个成员的对偶结构: Dim MakePair = Function(u, v) Function(m) If(m = 0, u, v) Dim pair = MakePair("Asdf", "231") Console.WriteLine(pair(0)) "第一个字段 Console.WriteLine(pair(1)) "第二个字段 您可以看到,MakePair不仅仅自己是一个函数,它调用之后还会返回另一个函数,而后者则捕获了MakePair传入的参数,形成了一个保存数据的结构体。这种返回函数的函数称作高阶函数。高阶函数就是函数式语言与普通命令式语言中函数的最大不同,它将函数的抽象能力提高了一个档次。 下面我们来看一个更难,更神奇的例子:怎么用Lambda表达式实现递归。众所周知,Lambda表达式是匿名函数,因此他们不能通过引用自己名字的方式进行递归。有人可能想到,将承载Lambda表达式的委托变量捕获到Lambda的闭包中实现递归,但这样违背函数式编程的精神。到底能不能实现纯粹Lambda的递归呢?答案是肯定的,但是需要一个特殊的东西——不动点组合子。 不动点用一句通俗的话表示就是将它作用于任何函数f后,等同于将f再次调用到整个体系本身上的效果。假设用Y表示不动点组合子,那么Y(f)的效果就等同于f(Y(f))的效果。也就是说我们把调用的体系Y(f)又完整地作为参数传递给了f函数。聪明的你就会发现,这个Y组合子可以用来实现函数的递归。只要让f的内部逻辑将自己的参数视为下一次调用的自身函数即可实现。如此神奇的Y在VB中到底是什么样子呢?如果用无类型的写法,它类似于这样: 源代码网推荐 源代码网供稿. |
