程序设计语言中的动静融合

 

程序设计语言概论课的作业,1万多字,其中可能有8000多我自己写的吧。贴在这里,把摘抄不是自己写的部分删掉只留标题,所以有些段落可能不够通顺,姑且看之。Word贴过来,格式也许有问题

——————————————————-

动态语言与静态语言概述

我们常用静态语言和动态语言来划分众多的编程语言,其实这种划分并没有明确的界限。相对明确的概念是语言静态特性和动态特性的划分。动态特性是指在运行时刻的语言特性,主要依赖解释器或虚拟机,而静态特性则依赖于编译器。

(对于语言来说,动态这个词在不同的语言中有着不同的表现。对比多种编程语言我们会发现在,某些语言中,大部分关系是在运行之前确定的,即在编写代码,编译或者连接的时候就已经确定了――也就是说静态特性多一些;另一些语言则尽可能地推迟确定的时间,直到运行时才确立许多关系――动态特性多。较多采用静态特性的语言比较倾向于静态,反之就比较倾向于动态。任何一种编程语言都可以看作具有某种程度的动态特性,比如最简单是一个变量可以运行时改变它的值,可以说它也具有动态特性。而Smalltalk和Lisp这样的语言因为可以运行时改变自身的结构甚至是函数(方法)的定义,我们认为这个语言具有更强的动态特性。)――注:这段应该是抄自《程序员》,删了就不通了,留在这里

早期的程序设计语言(这里主要指高级语言)偏向于极端比如Fortran极端静态,相对的Lisp极端动态。然而下面我们会论述随着程序设计语言的发展,纯粹的静态语言和纯粹的动态语言间有一种相互吸收彼此特性的趋势。特别是随着Java之类的同时包含大量动态特性和静态特性的程序设计语言的出现,使动态语言和静态语言的界限愈发模糊。

(从Fortran -> C/C++ -> Java/C# -> Smalltalk -> Python/Ruby -> Lisp,动态特性从左到右不断加强。比如最原始的Fortran语言中没有堆栈,因此无法在运行时动态分配内存,甚至都无法进行函数递归调用;C/C++进一步,拥有堆栈,指针,能够灵活的在运行时动态生成对象(new语义)。Java和C#语言因为拥有很强的RTTI 功能,使得运行时动态识别、加载和管理类的能力大大提升。使得他们动态特性又前进了一大步。而Python和Ruby干脆可以在运行时动态改变类或是实例的结构或是定义。)――注:这段同上面注

这里我们依照习惯将Java、C#划分为静态语言,而别它们更动态的语言划分为动态语言。由此给出动态语言定义:可以在运行时刻改变变量类型和修改程序结构的程序设计语言,这里运行时刻改变变量类型主要指动态类型特性,而运行时刻修改程序结构主要指可以动态创建、删除类或函数并且可以在类中添加、删除方法或属性以及修改方法的实现;不满足这个的既是静态语言。以下我们首先讨论对这个定义中明显区分静态、动态语言的特性:静态类型和动态类型,并且简述一下静态语言和动态语言的特点;然后论述程序设计语言发展中的动静融合趋势。

静态语言

静态类型简述(删)

静态语言的特点(删)

动态语言

动态类型简述(删)

动态语言的特点

语言动态特性可以为程序提供很多好处,比如:健壮性、可移植性等等,但随着Java吸收了这些特性后,这些好处也不再是动态语言所特有的。在下一节我们会逐一讨论Java所吸收的动态特性和它们所带来的好处。而这里我们主要考虑我们定义的动态语言的特点:动态类型和运行时刻修改程序结构,给程序设计和编写所带来的好处

动态类型最基本设计哲学在于赋予程序设计最大灵活性,不在编译期给程序添加不必要限制,并认为这样可以最大程度的提高开发效率。为了说明这点,我们可以参考一下MFC的实现。MFC(出于效率考虑,但不一定必要)没有使用在现代图形界面中经常使用的多态技术――多态显然是语言动态特性,而是采用一组宏来构造消息链和消息映射等等机制。不可否认MFC从实现来看还比较优雅,但大量的宏使用使得不通过wizard来手工构造一个MFC应用几乎不可能,而且不经过深入研究很难理解MFC的工作方式。对比之下,类似wxWindows之类的图形界面框架使用多态来完成消息映射,其开发和理解难度对比MFC都很低。虽然MFC使用宏来实现主要是出于效率考虑的结果,但这恰好反映出动态语言和静态语言使用是在开发效率(程序设计的灵活性、简单性)和运行效率权衡的结果。

Java等语言吸收了大量动态特性并且被广泛使用后大大提高了人们的开发效率,但由于没有我们定义的动态语言所具备的两个主要特性:动态类型和运行时刻修改程序结构,还是给人们的开发工作带来一定的限制。作为例子我们可以参考Hibernate和Ruby on Rails中的O/R Mapping模块。如今主流的O/R Mapping框架因为需要实现对领域模型进行透明的持久化,所以需要在运行时刻对领域模型进行监视,当其数据发生变化时表示“脏数据”。然而为了不在ORM框架和领域模型之间造成不必要的耦合,也就是希望领域模型可以以POJO来实现,这就需要在运行时刻修改领域模型的结构,在其中添加必要的监视“脏数据”的方法。这里Hibernate使用CGLIB+反射机制来实现这种修改,不论从实现还是理解的难度都比Ruby那种从语言层面直接支持的方式差了许多。而实际上Ruby on Rails正是因为其大量动态特性――主要就是动态类型和运行时刻对程序结构的修改――的运用大幅度提升了开发效率才造成了对J2EE社区的极大地震动。

Java――成功的动静融合

从程序设计语言出现开始,静态语言一直是工业界的主流(Fortran、C、C++)――相对来说动态语言则是学界和Hacker的最爱(Lisp、Perl、Smalltalk)――这主要是出于效率考虑的原因,动态语言大量的运行时刻特性造成其效率不彰,在硬件比较慢的时代这是必然的结果。然而随着硬件在摩尔定律的推动下飞速前进,加之软件复杂性越来越高,那种注重运行效率而忽略开发效率的程序设计语言既失去了依存的环境也不能满足人们的需要。长期以来静态语言一直在逐渐吸收动态语言的特性:递归的引入;动态分配内存;多态等等,但这种步伐一直比较缓慢。(其中兼容性也起了一些负面的影响,就因为在对C的兼容性和效率方面投入了极大的关注,一堆天才们也只能设计出C++这种庞杂的语言,而后来的经验表明与其关注语义方面的兼容性倒不如通过对遗产系统的桥接来得直接,也更能卸掉包袱。)

对比后来的语言我们就会发现最后一个广泛流行的比较纯粹的静态语言C++太复杂了,以至于很少人可以正确使用它。即使只是以一定的运行效率的牺牲就来换取不用专家就可以实现绝对不会内存泄露的程序,我想在95年那个时候也会有很多人来做这个交易,何况动态特性还可以提供很多其它有助于开发效率的特性。所以Java的出现具有一定的必然性。

如果说语法是语言的皮,那么拔掉这层皮之后,实现看不出Java和C++有哪一点相似――Java和Smalltalk倒是更像一些――但Java在推出的时候仍然说自己是C++的更新换代,可以说Sun在这一点上做的很好,有一种假的兼容性吸引了广大C++程序员的目光。

根据我们前面的定义Java仍然是一种静态语言,但实际上Java(包括后来微软的Java实现C#、VB.NET)更像是一种动静混合的语言。它对语言动态特性的吸收可能比历史上所有静态语言吸收的综合还要多,下面我们来逐一阅览当时Java所标榜的特性,考察它们是动态或静态特性,并讨论对Java流行的影响。

我们将根据著名的”Core Java 2(7th) “中The Java “White Paper” Buzzwords部份来论述Java的特性:
简单:在做语言繁简的对比时,Java语言选取的对比标准一般是C++,对比后我们会发现我们不再有指针、头文件、结构、联合体、运算符重载(虽然必要)、虚基类、多重继承等等繁杂的概念了,Java真的很简单。遗憾的是同时我们也会发现几乎所有语言都比C++简单。另外虽然简单性不是从动态、静态角度上的特性,但简单而具有强大的表现力长久以来就是动态语言所追求的(相比动态语言Java那种每次从容器中取数据都要经过向下类型转化也就显得不那么简单了)――动态语言的始祖Lisp只用7个操作符就描述了自身。
纯粹的面向对象:Java的流行原因之一,这点的确和动静特性没关系……
分布式:Sun力推的特性,但事实证明同构平台下的分布式操作在绝大多数情况下并不需要,EJB的失败见证了这一点,而人们记住的就是Martin Fowler分布对象设计第一定律:“不要分布你的对象”。这点不是动态、静态角度上的特性,但也没给Java语言的流行带来帮助――如果不是起了反面作用。
健壮性:Java程序比C++程序更健壮,其中动态类型检查和虚拟机的内存管理量的特性功不可没――不会出现C++中的因为向下强制类型转化引发的奇怪错误,更是在理论上彻底消除了内存泄露(前提是虚拟机实现正确)。这是促使Java流行最重要的特性之一――使开发人员更关注于业务逻辑而不是时时刻刻想着堆栈――但其实动态语言从一开始就拥有这两个特性,在Lisp、Perl等等动态语言中从没见过谁关心过内存。(Lisp根本就不关心谁是冯•诺依曼)
安全:部分程度是上一点的附加特性,类似“栈溢出”等等的攻击手段被彻底杜绝了。
体系结构中立:与C++不同,Java代码被编译成byte-code,运行在虚拟机中。虚拟机有各种体系结构下的实现。但是这种体系结构中立对Java的流行有多大帮助很难说。如果说在Java刚出现C/S架构还在当道,那么Client的“一次编写,到处运行”很重要,但等到Java真正被广泛接受的98、99年,第二次浪潮已经来到B/S成为主流。所以很难说这种体系结构中立对于如今主流的Java应用还有什么帮助,可能的也就是嵌入式开发了。当然不论如何,动态语言一直是体系结构中立的,因为整个Java虚拟机的想法就是从动态语言这里发展来的,虚拟机本身就可以算是语言的动态特性。
可移植性:同上,只不过体系结构中立的同时还是OS中立(值得注意的是现在的J2EE应用已经被绑定在应用服务器中了)同样这种可移植性也是一种依赖虚拟机的动态特性。
解释型:毫无疑问,动态特性。不过对Java流行没有帮助。
高性能:(我没有考证这是从第几版开始被写入Java “White Paper”的,如果是一开始就被写入的,那么这就是个彻头彻尾的谎言。)原文的意思是说可以在运行时时刻根据程序运行的硬件环境对程序进行优化。首先可以肯定的是这也是个依赖虚拟机的动态特性;其次如果我们可以在每次部署的时候根据部署平台重新编译系统,那么可以肯定Java的byte-code肯定是要比经过编译优化的二进制代码慢的。可能有人会觉得做不到部署时重新编译,但要注意Java目前的主流应用是B/S架构的企业级应用,所以部署时重新编译是可能的;第三是这里Java有一个很大的贡献,Java的流行使虚拟机的效率在短期内大幅度提升(借助JIT等技术),并向人们证明了解释型语言的运行效率并不是想象的那么可怕。
多线程:提供并发的关键字,由虚拟机支持。较新的动态语言(Python等)都具有。
动态:主要包括动态类装载、反射,比如Java一个类的结构是由解释器在类动态装入时决定的,而在C++这样的静态语言中类在内存中的结构是编译时刻确定的,所以在类中增加一个实例变量或一种成员函数后,引用该类的所有子类都必须重新编译,否则将导致程序崩溃。这里Sun本身也承认Java是一种比C/C++动态得多的语言。原文如下”In a number of ways, Java is a more dynamic language than C or C++. It was designed to adapt to an evolving environment. Libraries can freely add new methods and instance variables without any effect on their clients. In Java, finding out run time type information is straightforward. “

总结一下我们会发现真正对Java流行起作用的语言特性(排除了分布式),除了纯粹面向对象特性(实际上打了折扣,模块、类本身和函数都不是对象,不能以一致的方式来处理,比Smalltalk、Python弱了一筹)之外,Java标榜的所有特性都可以被归为动态特性,而这些特性大部分依赖虚拟机。

如果我们对比一下主流的动态语言Perl、Python和Ruby就会发现Java除了具有静态类型特性和不能在运行时刻修改程序结构之外――后者主要指不能动态改变类、方法的声明等等,这其实是静态类型的一个副作用,比如静态类型提供编译时刻检查使程序不能调用类中还不存在的方法(实际上通过反射和CGLIB扩展字节码可以实现运行时刻一定程度修改程序结构但这种模拟方式很迂回,也不够灵活)――从哪方面看都很像一个动态语言,但也就是静态类型这一点使Java仍然是一个极为动态的静态语言。

Python、C#――进一步的动静融合

从前面所述我们可以看到,Java以静态语言为依托同时大量吸取了动态语言的优势――加之成功的运作――使其在数年间迅速发展为最为流行的程序设计语言之一。

然而自从Java诞生以来――也许是受了“Java是一种简单的语言”这句话的桎梏――从1.1到1.4 Sun除了对Java语言中的瑕疵修修补补(优化虚拟机、修改类库中一部分不好的实现等等)以外,在语言核心层面一直缺乏大的动作。只是到了C#“兵临城下”之际才在Java 5.0(这里Sun的心虚一如这个版本号)加入了泛型、自动拆装箱、元数据等特性,而这些特性也都是已经在其他语言中广泛使用的了。

Sun公司在Java发展历程中的表现告诉我们,这家公司在企图有所创新时几乎总会失败(早期Java类库的实现,Entity Bean等等),而它在软件商业领域的表现更是让我们质疑它的能力(应用服务器、集成开发环境方面的表现,我们有理由认为作为Java的发明者Sun实际可能上并没有从Java的流行中获得了太大的利益),加之JCP这个看似民主的过程存在着其他民主过程的通病――各个大利益集团相互掣肘导致的缓慢,这些因素本来完全有可能让Java这颗新星迅速陨落。但幸运的是Java发展的早期那种对微软的抵抗态度,或者说它所标榜“自由”的“宗教信仰”拯救了它自己。开源社区蓬勃发展,以及其对Java语言的大力支持,使Java仍然是今天最值得使用语言――试想如果失去众多的优秀开源产品(Eclipse、Hibernate, etc),而这些产品支持的是另一阵营(PHP、Python甚至是.NET),那将是怎样一种局面(虽然我不否认偶然中的必然性)。但需要明了的是Java在语言层面的革新动作是始终微缓,而且不值得人们报以期望。而通过下文我们会发现仅仅通过不断扩展的类库是不足以(至少是不能直观的)适应语言发展变化。

这里我们首先指出我们认为语言的发展趋势会是进一步动静融合,而从前面对Java的分析可以看到Java和主流动态语言最大的不同就是静态类型和动态类型,所以进一步动静融合的重点就在于兼取静态类型和动态类型的优势。下文将通过对Python和C#语言目前演变趋势的描述来说明这种趋势。

Python,静态类型的引入

05年年初Python的作者Guido在其blog上发表了数篇文章(Adding Optional Static Typing to Python, etc)论述了在Python中引入可选静态类型的可能性,而后在Python3.0 Plans的文档中Core language部分出现“Add optional declarations for static typing”的目标。下面我们首先叙述一下这种特性,然后讨论其引入带来的变化和好处。

Guido在其blog的文章Optional Static Typing — Stop the Flames中说明Python可能添加4种语言特性来支持可选静态类型,依次是:
参数、返回值类型定义,Argument and return type declarations
属性类型定义,Attribute declarations (maybe)
接口定义,Interface declarations
基于契约设计,Design by contract (maybe)

实际上我们注意到在目前的版本中Python类型判断运用的实际上是一种被戏称为Duck Typing的方法――Duck Typing的名字来自这句话“If it walks like a duck and talks like a duck, it must be a duck”。例如在Python中我们定义一个函数f
def f(i):
    print i.value
这样当我们调用f时传入的参数可以是任意类型,但如果运行时刻发现i不包含value的属性(it is not a duck),解释器就会抛出一个异常。这种动态类型为Python语言提供了极大的灵活性。

但是这种灵活性不是没有代价的,强大的动态类型也使Python丢失了大部分静态类型语言所拥有的便利,这包括:
文档生成,类型可以帮助我们更确切的描述参数和返回值,而动态类型导致需要大量文字来说明
IDE的帮助,现代IDE提供强大的智能完成和代码生成功能,这经常需要变量有确定的类型
优化,编译器可以利用类型信息来进行优化
反射类型信息,通过反射可以在运行时可得到函数参数和返回值的类型,利于外部库进行函数调用(特别在外部库是静态类型时,比如数据库桥,这尤为重要),尽管动态语言的反射能力一般都非常强大,但如果其本身的语义不支持函数参数返回值类型,那么也是无能为力
静态类型检查,通过早期检查提高程序的健壮性,而如今设计模式的发展已经可以很大程度上弥补静态类型所导致的紧密耦合出现。而对于大型项目,特别是多个模块同时开发的项目,静态类型接口可以作为一种有效的契约来约束各个模块的行为,而静态类型检查有效地保证了这种约束

事实上可以认为前三点只能说是引入类型带来的额外好处,而真正重要的是则是后两点。从前面论述可以看出对于大型项目失去静态类型是一件非常不利的事情,这完全可以抵消使用高灵活性动态语言所带来的生产率的优势。在此我们深化本节开始的说明:作为一门更进一步的动静融合程序设计语言最重要的特性就是兼具动态类型的灵活性和静态类型的可靠性。

这两点看似矛盾,实际上如果我们将这两点放到不同的粒度上去考虑,在一个粗粒度的级别上用静态类型描述接口,而在内部是使用动态类型编写程序,可能在将来的Python3.0中我们可以这样来编写程序:
interface IBussinessLogicFacade:
    def insertOrder(order: Order, date: Date) -> boolean: #静态类型的接口
        “insert a Order return true if succeed”

class BussinessLogic(IBussinessLogicFacade)
def insertOrder(order, date): #动态类型的实现
    …
这样我们就既能有效的利用动态语言来提高生产率又能利用静态类型来巩固程序。

但是如果我们详细阅读Guido年初的文章,我们会发现Guido最后的定稿中的可选静态类型的语言特性只是他开始提出的全部想法中很小的一个子集,大量有趣的特性没有出现在Python3.0 Plans中,这包括泛型、重载等等,而且Guido还提到Python可能不会在language core级别添加静态类型检查等等功能――静态类型可能更多的是为外围的库(比如Pychecker,一个Python程序的检查工具)提供支持,这里的静态类型可能更多地沦为一种元数据的描述手段。这里我们可以体会到Guido的小心翼翼,毕竟对于动态类型为核心的语言在其上包装一层静态类型的壳,如果一不小心就会丢失掉动态类型的好处而还要承担大量反射带来的性能损失。而且一个兼具动静融合的语言可能同时需要强大的编译器和解释器,Python社区作为一个缺少大厂商支持的社区,如果语言设计过于复杂就可能超出其能力实现的范围就可能沦为下一个Perl 6。

C#,超级编译器

微软之于JCP,与其说是专政之于民主,倒不如说是私企之于国企更为恰当。对比微软来说,JCP做对一件事几乎和做错一件事一样难,当然大部分时间他什么也不做。反之微软开发技术每次的更迭所带来的变化总是令人乍舌(Dos到Win32,Windows DNA到.NET),而这也是微软最遭开发人员诟病的地方之一。

类似的,对比属于JCP的Java和属于MS的C#(实际上也包括VB.net,简洁起见以下略去),我们同样发现C#在语言核心变更上的激进程度令人惊讶。实际上如果我们注意Java以及更早的C++就会发现这些语言都是在努力保持着语言核心的稳定和类库的激进。但是不应忘记的是与它们不同C#事实上的一种厂商专有的语言(虽然被标准化),所以从微软的角度来说变更语言核心和变更类库是可以完全从技术上考虑,这使得C#较之商业语言更像一种实验室语言――并不排除这是MS打击Java的一种战略。唯一令人担心的是语言核心如此剧烈的变更会打击开源社区的积极性(比如Mono)。

回到正题,我认为微软很久之前就注意到了动态语言的重要性。实际上远在Win32的时代,VC+VB方案流行的时候,VB就可以称为一种基于对象的动态脚本语言。而微软从COM开始就致力于不同程序设计语言间的无缝集成,到了.NET的时代从设计之初不同语言的集成就是其重要的目标,所以我们也就看到了大量的将其他程序设计语言向.NET平台迁移的项目(Perl.net, IronPython, etc)。但也许现在MS已不再热衷于动态语言与静态语言在虚拟机一级的集成了,因为从不久前微软发布了一个C#3.0的预览版本来看(实际上从C#2.0就已经开始)MS似乎想在语言层面让C#尽可能地吸收动态特性,塑造出一种可能是兼备动态类型的灵活和静态类型的可靠性的语言,而这正是我们认为的未来语言的趋势。以下简单浏览一下C#3.0的特性,并讨论一下C#和Python在动静融合方面思维方式的不同。

C#3.0最大的一个特点就是赋予了编译器强大的类型推导能力,C#3.0的大部分新特性都需要类型推导能力。比如隐式类型局部变量(implicitly typed local variable)允许我们编写这样的代码:
var i = 5;
var s = “Hello”;
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary();
这里编译器会帮助我们根据初始化语句推导出变量合适的类型,而匿名类型特性以隐式类型局部变量为基础可以产生更惊人的效果:
var p1 = new { Name = “Lawnmower”, Price = 495.00 };
var p2 = new { Name = “Shovel”, Price = 26.95 };
p2.Price = p1.Price
p1 = p2;
注意p1和p2是可以相互赋值的。可以看到这段代码很像动态语言的代码。另外C#2.0引入匿名方法,C#3.0更是引入lambda表达式(其中隐性局部变量仍然起了重要的作用)这种函数式编程的特性。这些都反映出C#在吸收动态特性方面的努力。

另外值得我们注意的是C#进行动静融合时的思维方式。为了对比我们首先看Python。实际上一般来讲我们认为静态方面的语言特性由编译器来完成,反之动态方面的语言特性由解释器(虚拟机)来完成。Python本身作为一种动态语言,它在试图吸收静态特性时首先想加强的是它的编译器。直观地想,我们会认为C#既然拥有CLR这样强大的解释器(已经在其上实现了几个动态语言,证明了其能力)那么当它吸收动态特性时理应应用这一点――如果这样做C#3.0和Python3.0可能就没什么不同了。

但事实不是这样,这里不得不承认C#3.0在这方面是另辟蹊径,C#把这些动态特性大部分推给编译器――类比C++的静态多态(泛型),利用编译器的推理能力,使程序开始运行时仍然像个静态类型语言,但在编写时可以忽略大量类型的桎梏而很像动态语言。这里最大的得利就是效率――Python、Ruby总是被人们批评为“世界上最慢的语言”,因为今天硬件的表现还不足以让我们忽略反射的损失(虽然也许10年之后谈反射的性能损失就像现在谈论虚拟机的性能损失一样)。

当然这样的实际上是“伪动态类型”的程序设计语言的灵活性到底如何还有待大范围应用后考验,但微软在这里已经走出了第一步,而C#也符合了我们开始说的实验性语言的地位。

2005-12-8

 

程序设计语言中的动静融合》上有2条评论

  1. 小老弟还没毕业就对语言有这么深的认识。汗!惭愧,和你一比我差老了。<br>

发表评论

邮箱地址不会被公开。