一个C#中的例子——lambda表达式

我们多次提到,当前来说,Web开发领域,开发工具(或者叫平台)的“三巨头”是 .Net、JAVA和PHP。如果用.Net,那么一般用C#语言(或者VB),在C#语言3.0版中,引入了一个非常重要的新特性称为“lambda表 达式”。首先来普及一下“lambda表达式”的概念和作用。lambda 是一个希腊字母“λ”的英文读音,我们中国人一般念作“拉姆达”。

首先,在C#中,可以用 lambda表达式来定义一个匿名的函数。例如

x => x%2==0

就定义了一个匿名的函数,这个函数的作用是判断一个参数是不是偶数。其中 “=>” 是C#中用用于定义lambda表达式的运算符,他前面的x是函数的输入参数,他后面是返回的结果,当x是偶数,即x除以2的余数等于0时返回true, 否则返回false。那么定义了这么一个lambda表达式有什么用呢?还是看一个例子。

比如说,有一个一维数组,保存着若干整数,现在需要计算出其中大于0的个数。那么按照传统方法可以这么写:

int c=0;
foreach(int i in numbers)
{
if(i>0) c++;
}

而如果在C#3.0中,使用 lambda表达式,代码就可以缩减到这样一个语句:

int c = numbers.Count(x => x>0 );

其中,Count() 是数组类型的一个方法,它的参数是一个lambda表达式,通过这个lambda表达式可以过滤参加统计的元素,就是说,只有大于零的元素才进行统计,这样,一句话就可以非常清晰地完成了上面的任务。

而这仅仅还是一个非常简单的要求,如果是这个例子的要求更复杂一些:“将一个整数数组中的所有正数选出来,各自求出其平方,然后按大小排序,构造出 一个新的数组”。那么这段程序可就需要做不少事情了,首先要对数组中的所有元素进行过滤,筛出大于0的数,然后每个数求出他的平方,然后对他们进行排序, 并且要构造一个新数组并填充结果。而在C#3.0中,通用可以使用一个语句完成上面的任务:

int[] n = numbers.Where(x => x>0 ).Select(x=>x*x).Orderby(x=>x).ToArray();

可以看到,上面的语句中,出现了3次 lambda表达式。Where、Select、Orderby这个三个方法调用,分别实现了过滤、平方、排序,他们都是以 lambda表达式作为参数传递到方法中,可见它的灵活和方便。

lambda表达式的数学来历

java兰姆达 filter 兰姆达的公式_java兰姆达 filter

我 们看到了lambda表达式的作用,很容易我们会想到,为什么他们叫做“lambda”表达式这个“奇怪”的名字呢?实际上,这就倒回去80年了,在计算 机还处于“纸上谈兵”的年代,20世纪30年代,美国数学家阿隆佐.丘奇(Alonzo Church, 1903 – 1995)给出了一套用于研究函数定义和应用的形式系统,称为”λ演算“(lambda calculus)。

如果没有学习过相关内容,要完整地理解 lambda 演算,并不是很容易的一件事。这里给出一个最简单的例子,在 lambda 演算中,每个表达式都代表一个只有单独参数的函数,这个函数的参数本身也是一个只有单一参数的函数,同时,函数的值是又一个只有单一参数的函数。函数是通过 lambda 表达式匿名地定义的,这个表达式说明了此函数将对其参数进行什么操作。

例如,一个函数 f(x) = x + 2 ,这是我们都很熟悉的书写方式,表示自变量(参数)为x,函数的值为“x+2”。如果用 lambda 演算表示,他的形式就变为了: λ x. x + 2。这里λ是一个固定的符号,后面的x表示参数,圆点后面表示函数的值是x+2,而 f(3) 的值写作 (λ x. x + 2) 3。更多的 lambda 表达式 相关的内容,这里不再介绍。

那么如果仅仅是这么性形式上更换一个写法,当然也就没有什么实际意义了,而这么做的巨大意义在于,基于这样一套非常简明的规则,可以把“计算”这件事说清楚,即什么是可以计算的,以及如何使用通用的方式描述一个“计算”的过程?

就好象CSS为了描述页面布局,而设计出一套“盒子模型”,它是把页面布局中的最通用、最基本的一些特征提取出来,形成的若干规则。同样,”λ演算“是一套“计算的模型”,它是对计算的本质特征的描述。 lambda 演算与现代的计算机之间,还看不出直接的联系。而到了阿隆佐.丘奇的学生阿兰.图灵(Alan Turing)的时候,就把二者直接关联起来了。

java兰姆达 filter 兰姆达的公式_编程语言_02

图灵机(英语:Turing Machine,又称确定型图灵机)是英国数学家阿兰·图灵于1936年提出的一种抽象计算模型,其更抽象的意义为一种数学逻辑机,可以看作等价于任何有限逻辑数学过程的终极强大逻辑机器。

也就是说,图灵“从理论上”设计出了现代的计算机,并给出了计算机的具体运行方式。而这个运行在“纸面上”的计算机,仅仅用几行文字就可以描述清 楚。在图灵发表了他的关于图灵机的理论十年之后,真正的计算机被真正制造出来。直到今天,我们使用的计算机,本质上看,不管你用的是“奔腾”还是“酷 睿”,仍然都是在在图灵机的模型上建立的,这就是数学的力量。人类的最重要的思维能力之一是“抽象”的能力,牛顿从苹果落地“抽象”出万有引力定律,图灵从千变万化的计算中“抽象”出图灵机,包括CSS的设计者从页面布局中“抽象”出合资模型,本质都一样的,就是从纷繁复杂的表象之中,看到了隐藏在现象背后的本质的规律。

由此,我们可以看到,数学的作用,正是从纷繁复杂的现象中寻找事物最本质的规律。

至于Lambda表达式,C#中引入并不算早的,在它之前,就已经有很多现代的编程语言中都实现了Lambda表达式。更为重要的是,我们当前进行开发,大多使用的C语言为代表这类过程性语言,实际上,还有一大类编程语言,与之区别甚大,称为“函数式语言”。

比如现在很热门的Erlang,以及很古老的Lisp,在国外大学教学中非常流行的SCHEME,甚至微软都已经推出了自己的函数语言F#。而全世界的第一个函数语言,就是这个并非为计算机设计的”λ演算“。 实际上,在计算机高级语言出现之初,函数语言和过程性的语言是同时开始的,但是几十年来的普及程度,过程性语言远远超过了函数式语言,具体原因大概是函数语言并没有过程性语言方便。但是世道就是这样有趣,随着计算机硬件的发展,现在多核CPU的出现和普及,并行计算的要求越来越高,又使得函数式语言的优势大大增加,从而使函数式语言大为热门。所以,研究历史、对比当前,发现到问题的本质,实在是很有趣的一件事。

如果对编程语言感兴趣,可以参考《编程语言的入门科普》和《编程语言分类学》。

再看C#中的lambda表达式

上面我们了解了C#新引入的“lambda表达式”这个特性,以及他的来历和数学基础,也简单演示了一下在C#中使用“lambda表达式”的作用,但是如果仅仅是为了节省几个循环语句,是代码简洁一些,就太小看了“lambda表达式”的作用。实际上微软引入“lambda表达式”的意义远不止 于此,还有更大作用,这里就不再深入研究了。

写过一些程序的,比如用C语言或者C++、C#语言的读者有没有考虑过一个问题,尽管我们在程序中计算一个表达式的值很容易,比如这样一句话:

int x=3*(2+3);

就给x这个变量赋值为15了。

但是如果要您写一个程序,实现用户输入一个数学表达式的字符串,然后计算出结果,可就不那么容易了。写一个能够计算加减乘除、带有括号的四则运算的程序,至少要在计算机系学到3年级的《编译原理》课程,才够用的。为什么呢?因为里面包含了很复杂的逻辑的,比如你需要能够把一个字符串,解析为运算的操作数和运算符号,同时还要考虑,先乘除后加减的规则,以及括号的处理。若你可以没有学习任何相关基础的情况下,自己能够成功地写出这样一个程序,说明你绝 对是个非常非常聪明的人。

理论上说,计算机程序中包括两个部分:“数据”和“指令”,当我们把一个四则运算的表达式存储在字符串中的时候,它仅仅是一些数据,而不是指令,要真正计算出结果,需要把这个数据转换成指令,才能够进行计算。

因此,如果从更深层次看待这个问题的话,C#引入“lambda表达式”、以及“表达式树”这几个新特性以后,从基础层面,在“数据”和“指令”之 间搭建了一座桥梁,可以非常方便地相互转化。当一个表达式的逻辑通过“表达式数”以数据的形式保存以后,就可以方便地对他进行各种操作。具体内容这里不再 详细讨论。

java兰姆达 filter 兰姆达的公式_lambda表达式_03

当 然这方面还有很多工作可做。实际上,由Anders Hejlsberg主持的C#语言从1.0到即将发布的4.0,一直保持着相当快速的的前进步伐,C# 1.0实现了代码的托管,C# 2.0实现了泛型,C# 3.0引入了LINQ,C# 4.0将引入动态特性。一步一个脚印,而且每一个新特性都为后面的发展打下了坚实基础。

总而言之

在上面,我们结合C#语言中的一个特性,说明了一下它背后的数学背景。可以看出以下的一些结论,希望对读者有所帮助:

计算机科学的本质是数学

计算机成为一种科学,其实历史很短,不过几十年的时间。尽管现在的计算机已经成了日常消费品,而当初计算机科学的先驱们可都是大数学家。当然现在计算机以及软件开发已经成为了一个巨大的产业,很多很多工作,都不需要数学家才能胜任了。否则,依靠着几个数学家,这么多事情可做不过来啊。

但是有一点我们要认清的是,越是重要的、底层的、基础性的工作,和数学的关系越密切。因此,我们作为普通人,可以看看自己的能力到哪层,就去寻找适合子的工作。

软件开发人员要学到什么程度才够用呢?

就像上面我提到的,现在整个产业已经变得巨大无比,因此层次也非常多,已经不存在一个明确的界限了,无论你有的基础如何,总是可以找到适当的位置的。当然,如果你希望做一个程序员,数学还是一个需要重要因素的。做程序的人数学不好,就好象唱歌不认识乐谱,或者绘画不懂素描,对你的影响是很大的,

至于应该如何学习数学?我的感觉是: 1)先侧重于“广度”,2)然后再有重点地考虑“深度”,3)最重要的是提高学习能力。

第一句话的意思是:先侧重于“广度”的意思是,你先不管能理解多少,至少把那写必备的基础,都略知一二,即使不是真懂,至少也要知道一些重要的概念、大原则、大的思考方法。

第二句话的意思是:然后再考虑“深度”的意思是,在大致了解的基础上,在找一些相对直接有用的,深入地学习。

第三句话的意思是:任何人都不可能什么都掌握,很多东西都是在用到的时候,才会去具体研究学习,因此学习能力才是最重要的。