商讯信箱
用户名: @
密  码:   注册|忘记密码
登录
个人用户经销商
您的位置:首页 > 技术频道 > 正文

VS2008亮点 用Lambda表达式进行函数式编程

作者:装配脑袋  2007-11-26
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中到底是什么样子呢?如果用无类型的写法,它类似于这样:
Dim Y = Function(f) _ 
(Function(h) Function(x) f(h(h))(x)) _
(Function(h) Function(x) f(h(h))(x))
   这时Y1就是一个真实可以运行的不动点组合子。我们来看一个用Y实现递归的例子:
Dim fact = Y1(Function(self) Function(n) If(n = 0, 1, n * self.Invoke(n - 1)))
   注意,fact这个函数是一个真正的匿名函数,它的内部并没有引用自己的名字,但是它采用不动点算子实现了递归。我们知道Y1作用于任何函数数,都等于将后者再次作用于整个体系上的效果。所以上述代码进行这种规则运算后就等同于:
fact = Function(n) If(n = 0, 1, n * fact(n - 1))
   很简单,只是将原代码中self位置换成代表整个体系Y1(f)的fact,就可以得到这一结果。很显然,这就是计算阶乘的递归算法。调用fact函数,你就能真实得到运算结果(记住参数不要太大。。。)。马上试验一下吧!是不是很神奇。虽然不太搞得懂Y内部的奇妙逻辑,但程序真的跑起来了!

   虽然程序跑起来的,但使用.Invoke的后期绑定语法效率不高,我们下一步要得到一个具有一定类型约束的版本。这需要很强的逻辑分析能力,各位有兴趣可以尝试一下,我们最后得到的结果如下:

Dim Y = Function(f As Func(Of Func(Of Object, Object), Func(Of Object, Object))) _ 
(Function(h As Func(Of Object, Func(Of Object, Object))) Function(x) f(h(h))(x)) _
(Function(h As Func(Of Object, Func(Of Object, Object))) Function(x) f(h(h))(x))

Dim fact = Y(Function(self As Func(Of Object, Object)) Function(n) If(n = 0, 1, n * self(n - 1)))

Dim result = fact(4) '真的能工作哦
   诸位一定被这里复杂的类型吓到了。这下可以理解我前面说VB的匿名委托与弱类型特性有多么重要了吧。用这个方法获得的递归算法大概要比普通的递归慢一倍左右,我们认为这样的结果已经很好了,至少他们是同一个数量级的。(当然,不是说真实程序也非得这么编,这里只是在学习它的奇妙特性)。现在,大家都已经做过实验了,有没有人有兴趣研究Y在C#中的实现呢?首先我必须提醒C#实现Y是比较困难的!需要脑子里有特别清楚的类型概念和逻辑思维能力才能正确写出来(然而真的能写出来!不要怀疑)。我把这个问题留给感兴趣的人来挑战一下吧。

   看了以上的例子,我想以前对函数式编程不了解的读者也已经感受到函数式编程的奇妙之处了吧。而且更进一步地研究还有更神奇的思想在里面。我们平时享受Linq的便捷语法时可能不曾想过这么深层的函数式世界,所以偶尔思考一下可能就能激发出更多的灵感,从而写出更多前所未有的程序来。欢迎各位参与讨论。



1 2
【内容导航】
第1页: 第1页 第2页: 第2页
©版权所有。未经许可,不得转载。
[责任编辑:胡铭娅]