1.4 了解编程语言的发展趋势

自从上个世纪中叶计算机出现以来,编程语言走了一条长长的发展之路。

C#之父Anders Hejlsberg在2010年所做的一个名为“C# 4.0 and Beyond演讲视频见:http://channel9.msdn.com/posts/matthijs/C-40-and-beyond-by-Anders-Hejlsberg/.”讲演中介绍了他对于编程语言发展趋势的判断,指出了现代编程语言应该拥有的三大特性(见图1-14)。

下面向读者介绍一下这三大特性都包容了哪些内容。

图1-14 现代编程语言的三大特性

1.4.1 声明式编程风格

声明式(Declarative)编程特性体现为告诉计算机“要干什么(Do what?)”,而非“怎么干(How to do?)”。

LINQ就是声明式编程的典型实例,如果将它与标准的面向对象编程做个对比,就很清楚这两者的不同了。

下面举一个实例(参看示例项目ImperativeVsDeclarative)。

假设现在有一个Student对象集合保存了一批学生的信息:

public class Student
{
    public string Name { get;set;}
    public string City { get;set;}
}

需要按City属性对其进行分组统计(图1-15)。

图1-15 对学生数据进行分组统计

下面先介绍一下经典的面向对象实现方法,再与LINQ做个对比。

先定义一个数据结构用于保存数据分组:

class StudentGroup
{
    public string City;
    public List<Student> Students=new List<Student>();
}

编写以下方法完成整个数据处理工作:

static void GroupStudentByCity()
{
    var students=GetStudents();//获取学生对象集合
    var results=new List<StudentGroup>();//用于保存分组结果
    //遍历学生对象集合
    foreach(Student student in students)
    {
        //是否已有此分组?
        StudentGroup res=results.Find(
                item=> item.City==student.City);
        if(res !=null)//有,则向现有分组追加学生
            res.Students.Add(student);
        else//无,则新建一个分组,并且将当前学生对象加入
        {
            StudentGroup newRes=new StudentGroup()
                { City=student.City };
            newRes.Students.Add(student);//为新分组添加学生对象
            results.Add(newRes);//添加新分组
        }
    }
    PrintResult(results);//输出结果
}

上述代码清晰地告诉计算机:第一步做什么,第二步做什么……拥有一个明确的执行流程,并且我们可以从代码中清楚地看到整个数据处理逻辑。

以下为使用LINQ的版本:

static void GroupStudentByCityUseLINQ()
{
    var results=from student in GetStudents()//指定要处理的数据
            group student by student.City into grp//这些数据需要分组
            select ProcessGroup(grp);//对每个分组进行一些加工
    PrintResult(results);//输出结果
}

可以看到,使用LINQ编程,只需告诉计算机我们“要什么”,而让计算机自己去决定具体的步骤。

通常,我们将那种“经典的”、需要规定好计算机具体工作步骤的编程语言称为是“命令式(Imperative)”的,它与具有“声明式(Declarative)”特性的编程语言对比如图1-16所示。

在Anders Hejlsberg看来,具备声明式编程风格是现代编程语言的一个发展方向。

图1-16 声明式编程风格与命令式编程风格的对比

1.4.2 动态性编程特性

所谓“动态性(Dynamic)”,其实就是将一些原本在编译时完成的工作(比如进行类型识别、生成方法调用指令等)推迟到程序运行时才进行。

比较知名的动态编程语言包括Ruby和Python,Web开发中常用的JavaScript也可以归入动态编程语言的范畴。

使用动态语言编程的好处主要是灵活,开发效率较高。

.NET 4.0以前,CLR是针对静态编程语言(如C#)而设计的,无法直接运行Ruby和Python等动态语言开发的程序。

.NET 4.0引入了一个“动态语言运行时(Dynamic Language Runtime,DLR)”,在CLR之上提供了一个动态语言的运行环境,从而允许Ruby等动态语言编写的程序在.NET平台上运行(见图1-17)。

图1-17 .NET 4.0引入的“动态语言运行时(DLR)”

有趣的是,C#和VB.NET等“传统”语言现在也可以利用DLR的功能,具备部分的动态语言特性,比如C# 4.0就引入了一个新的dynamic关键字用于定义“动态类型检查”的变量,由此可以写出以下C#代码:

dynamic d=1;
dynamic result=d.DoSomething();
Console.WriteLine(result);

变量d保存的是一个整数,默认情况下它不可能有一个DoSomething方法除非为其定义一个扩展方法,本书3.6节介绍了扩展方法。,然而,上述代码却可以顺利地通过编译,直到运行时才报告引发了一个RuntimeBinderException异常。这说明C#编译器“看到”dynamic类型的变量时,它不去检查此变量的真实类型,而将对“d.DoSomething()”这个表达式的类型推断推迟到程序运行时。

示例项目DynamicExample展示了更多的动态编程技术细节,请读者自行阅读。

交叉链接

本书第23章《迈进动态编程的世界》对C# 4.0所引入的动态编程特性和DLR的基本原理进行了系统的介绍。

1.4.3 支持并行程序的开发

随着计算机进入多核时代,编写支持多核并行的软件成为潮流。因此,编程语言也要进行相应的变革,以提升并行程序的开发效率。

在.NET 4.0中,引人注目地添加了一个“并行扩展(Parallel Extensions)”,它为所有.NET编程语言提供了一个开发并行程序的平台。在编程语言层面,主要体现为将“传统”的LINQ增强为支持并行功能的“并行LINQ(Parallel LINQ,PLINQ)”,由于LINQ本身可与编程语言无缝集成,这实际上相当于“间接地”给.NET编程语言扩充了开发并行程序的能力。

将LINQ转换为PLINQ非常简单,很多情况下只需添加一个新的AsParallel子句即可,例如,我们可以很方便地将前一小节中对整数数组进行处理的LINQ查询转换为并行查询:

var results=from student in GetStudents().AsParallel()
    group student by student.City into grp
    select ProcessGroup(grp);

上述代码在多核CPU上运行时,会自动地划分为多个子工作任务并行执行。其中关键的一点是这些代码并不假设一定会在多核CPU上运行,也不假定CPU执行核的个数,一切都是“透明”的,用户将会发现这些代码在多核CPU上执行会得到更好的性能,但在单核CPU上也能得到正确的结果。

交叉链接

本书第19章《并行计算技术基础》全面介绍了.NET 4.0所引入的并行扩展。.NET并行计算技术是建立于传统的多线程开发基础之上的,笔者建议对并行计算感兴趣的读者能通读本书第4篇《进程、线程与并行计算》的5章(第15~19章),有助于建立起一个比较系统完整的.NET 4.0并行计算知识框架。

1.4.4 .NET编程家族新成员——F#

.NET 4.0除了在基类库这个层面添加了对并行计算的支持之外,还引入了一种新的可以方便地开发并行程序的.NET编程语言,称为“F#”。F#本质上是一种函数式编程语言,拥有许多对于面向对象程序员来说非常有趣的特性,比如所有的“变量更确切地说,F#中没有“变量”的概念,只有“值”的概念。将一个标识符与值关联的过程称为“绑定(Bind)”。”都是只读的,广泛采用递归的方式编程等。

以下F#示例代码用递归的方式定义了一个函数fib,它可以求出Fibonacci数列中指定位置的数值(示例解决方案FibonacciForFS):

let rec fib x=
    match x with
    |0->0
    |1-> 1
    |_-> fib(x-1)+fib(x-2);;

定义好上述递归函数之后,“fib 10”就代表第10个Fibonacci数(它等于55)。

使用Visual Studio 2010创建一个F#项目之后,在代码编辑器中选中要执行的代码,按Alt+Enter键,就会在Visual Studio 2010下部的“F# Interactive(F#交互式)”窗口中看到其执行结果(见图1-18)。

图1-18 在Visual Studio 2010中开发F#应用程序

如图1-18所示,F#的开发方式类似于早期的Basic,即时输入,即时运行。

扩充阅读

这种即时输入、即时运行的开发模式有一个专门的术语,称为“REPL”,是“Read-Evaluate-Print Loop(读入→执行→输出 循环迭代)”的缩写,许多动态编程语言(比如Python)也拥有这种开发模式。在2010年微软技术日大会上,C#之父Anders Hejlsberg介绍说他们正在致力于将编译器的功能开放为编程语言直接可用的API,因此C# 的后继版本(C# 5.0)将能实现C#代码的“即时编译”和“即时运行”,这样一来,强类型的C#编程语言也可以像动态编程语言一样,采用REPL的开发模式。

F#其实是一个“混血儿”,它不仅支持函数式编程风格,也能开发传统的面向对象程序。

F#的独特特性(比如由于所有“变量”只读,从而在多线程环境下数据无需加锁)使得它在并行程序开发、实现一些典型的数学算法、需要对海量数据进行的数据处理等领域大有用武之地。

F#其实是现代编程语言的一个典型范例,C#也将走向类似的道路。

技术春秋

并不新鲜的函数式编程

F#虽然在计算机编程语言历史上是一种新的编程语言,但它所归属的函数式编程语言家族却有着长达五十多年的悠久历史。举个例子,1977年出现的FP就是一种函数式编程语言,它的设计者是John Warner Backus。

这里说说John Warner Backus,他是一名成就卓越的计算机科学家,设计了人类第一个高级程序设计语言Fortran,还发明了著名的“巴克斯范式(Backus Normal Form,BNF)”,它的扩充版本(Extended BNF,EBNF)是当前描述各种程序设计语言语法的常用工具。

所以,许多看上去很“新”的东西其实“很旧”,新路是接在老路后头的。