动态表达

来源:互联网 发布:税友软件集团南京 编辑:程序博客网 时间:2024/04/28 04:00
 

动态表达

developerWorks
文档选项
将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送

英文原文

英文原文


级别: 初级

Gary Pollice, 实践教授, Worcester Polytechnic Institute

2007 年 8 月 15 日

Journal icon 阅读关于当今使用的最流行的三种程序设计语言的资料 —— 动态语言 Perl、Python 和 Ruby。为什么使用它们?它们有什么共同之处,而它们的独特之处是什么?

来自 The Rational Edge

插图 从许多人选择进入软件开发领域时起,我们编写程序的方式就已经发生重大变化了。不仅开发工具变化了,我们用来表达解决复杂问题的解决方案所使用的语言也变化了。

现今,内行的软件开发人员都懂得多种语言。他们通常拥有使用至少一把程序设计语言开发应用程序的经验。看到一类称为动态语言的语言如何成为要实现许多应用程序所选择的语言是一件有趣的事情。

本月我们将看看这类语言,为什么它们流行,并且足够深入地讨论于其中三个 —— Perl、Python 和 Ruby —— 去了解一些它们的主要特性,以及它们彼此的区别。

什么是动态语言?

技术上,动态语言是能够在运行时能够进行修改的能力。这是什么意思?考虑 LISP,显示出该能力的最早的语言。LISP 主要的特性之一是它将指令作为数据对待。LISP 程序可以从文本文件中读取语句,并且好像它们是原始程序一部分一样执行它们。该程序还可以在其内存中创建新的表达式,并不知不觉地执行它们。举例来说,Perl 程序可以读入包含 Perl 代码的文本文件。然后,它可以执行该文件中的代码,就好像程序设计人员已经将代码作为原始程序中一部分写入了。LISP 是函数式程序设计语言,但这不是动态语言的必要条件。许多使用其他的程序设计语言范型的语言,例如面向对象的、命令式的,等等,也是动态语言。

动态语言经常有令用它们编写的程序非常简洁的高层次的结构。换句话说,程序设计人员不需要写很多源代码来生成十分复杂的程序。 1

许多人在真正谈论动态语言时,使用术语 scripting language(脚本化语言)。他们用这样的名字是因为许多动态语言用于编写那些可以为特殊目的而快速编写出的小型或中型的脚本。然而,今天,我们日益频繁地将这些语言用于更复杂的生产系统。

语言流行性

每个月,Tiobe Software 发布一个程序设计语言索引。 2 该索引根据一些因素给予程序设计语言的流行性的一些指示。该索引没有指出最流行的语言实际上是那些编写软件最常用的语言,该索引给出了软件开发团体感兴趣的不同语言的一些指示。 3

2007 年 6 月索引中的前十种语言中有五个是动态语言。这五种是 PHP、Perl、Python、JavaScript 和 Ruby。这些语言都有不同的目标。而就像许多经历过时间考验的优秀思想一样,这些语言已经扩展为支持许多其他的目标。实际上,对于许多适合选择动态语言的应用程序来说,相似处通常多于不同点。如果我们看看 Tiobe 索引中的头四种语言 —— Java、C、C++ 和(Visual)Basic —— 对于我们所面对的特别问题,通常有在选择其他语言之上选择一种语言的非常充分的理由。但对于许多动态语言来说则不然。选择经常归结为个人的偏爱,或者因为组织中现有的应用程序已经使用了某种语言才使用这种语言的需求。

在本文余下部分中,我将探究一下这三种语言的共同特性,并且还将介绍它们彼此独特的一些特性。您可能会问,为什么选择 Perl、Python 和 Ruby?最简单的答案是,我已经用这些语言实际地写了一些代码。我在 90 年代写了相当多 Perl 代码,最近几年我写了许多 Ruby 代码。我选择 Python 作为这两种的补充,因为,像 Perl 一样,它已经出现了一段时间,并且拥有一些我认为令 Perl 如此流行的特性,而这些特性现在我们在 Ruby 中也看得到。

我不打算非常彻底地谈论这三种语言,我仅仅希望您对每种语言有所了解。希望这将吊起您了解更多关于这些语言的兴趣,以及了解它们如何帮助您用较少的工作解决问题的胃口。

让我们开始此次旅程。

每种语言都有一个目的

我们在语言设计课程中学到的一件事是每种语言都应该有它的设计目的。虽然我看到过许多人“仅仅为了好玩”而创建一种新的语言,但是大多数语言设计者为了某个理由而设计它们。

语言设计者给出了三个创建新语言的公共的理由。

第一个理由是,现有的语言不能解决特定类型的问题。由于基本的计算方法和范型中的技术变更和进步,我们不断地面临着新的挑战。为了有效地适应变更,语言需要演进。当出现现有语言不支持的新东西时,程序设计人员将寻求用新的技术和方法解决问题的新途径,他们常常借助于开发新的语言。一个很好的实例是 Smalltalk 的创建,设计它是用来支持面向对象范型,并且提供在位图显示上构建图形应用程序所用的丰富功能集。

通常给出的开发新语言的第二个理由现有的语言已经陈旧并且肿胀的。程序设计人员经常谈论“代码散发的气味”。也就是说,代码像腐烂的鱼一样开始随着时间发臭。我们利用重构技术、设计模式和其他计算方法从我们的应用程序中去掉发臭的代码片段。语言经受的类似的疾病。随着为了处理一些新的技术或范型而向语言中添加新的特性,语言也随着时间不断的演进。当新的特性不是十分“适合”语言的其他部分时,它开始发臭。从某点上讲,聪明的程序设计人员决定是时候用新的语言来代替这个语言了。(Perl 虽然没有取代任何将要陈旧的单个语言,但是它组合了一些小语言,像 sed、awk 和 shell 脚本的特性,以及许多 C 风格的表达式。)

开发新语言的第三个理由是最少令人信服的:人们这么做是因为他们能够做,他们的语言映射出他们对世界,以及我们应该如何写程序的独特见解。虽然这是在智力上值得做的目标,但是它通常不能添加到有用的语言体中。大多数这类语言是没有未来的。然而,我将在文本中进一步讨论的 Ruby 的起源,可以用此理由部分地解释。

我们所关注的三种语言的目的是什么? 4 Perl 是三者中最老的,开始于 1987 年。Perl 的创造者 Larry Wall 是 Unisys 的程序设计人员。他曾经用像 Unix 程序 sed、awk 和 sh,以及用 C 写的程序的语言执行过各种各样的任务。他在从一种语言切换到另一种语言时有过挫败的经历,并决定创建一种完成这些语言能够做的,以及更多的事情的语言。他的哲学是,这种新语言应该很容易地在允许程序设计人员使用该语言最有效地解决困难问题的同时做简单的事情。首次使用 Perl 进行的许多任务包括文本处理和系统管理任务。

Perl 发展成为为 Web 应用程序编写 CGI 脚本的语言选择,并且在处理网络应用程序和数据库的程序设计人员之中有大量的追随者。Perl 的最新版本支持对象、封装和许多其他高级语言的概念。这种演进已经形成一个非常大型的语言,依我看来,它并没有被清晰地被结合成一个单一的编程系统。(当然,我期望 Perl 爱好者不同意这一点。)

Python 出现于 Perl 之后的几年。它的创造者 Guido van Rossum 想要一种语言,像 Perl 一样,可以很容易完成容易的任务,还支持困难的任务。然而,van Rossum 想要他的语言在速度和可表达性之上强调可读性,并且是面向对象的。Python 不比许多其他的动态语言慢,而 van Rossum 想要可读性和可理解性成为 Python 程序的主要属性。实施可读性的 Python 的特性之一是代码一贯缩进的需求。举例来说,以下图 1 中的两个实例是不同的:

if x == y :             if x == y :  print 'hello'               print 'hello'  print 'world'             print 'world'

图 1:Python 代码的两个实例

在图 1 中,如果 x 等于 y,那么两个实例都会打印出两个,第一行“hello”,第二行“world”。如果 x 不等于 y,那么第一个实例什么都不打印,而第二个打印出一行“world”。依我看来,这种“特性”似乎会引起错误,这就是为什么我没有用 Python 做太多程序设计的主要原因之一。

Python 已经演进并成为许多处理网络、多媒体、数据库和系统程序设计应用程序的开发人员的语言选择。Python 还是提供组件之间的粘合剂的好语言。像 Perl 一样,Python 与其他语言,例如 C 连接。现在,Van Rossum 在 Google 工作,他在那里继续致力于 Python,增强它在引起 Google 兴趣的应用程序领域中的使用。

Ruby 是三者中最晚出现的。它是由 Yukihiro “Matz” Matsumoto 开发的,此人从 1993 年开始研究该语言,1995 年将其发布。在某种意义上,Ruby 是由于我上面说明的第三种目的而开始其生命的 —— 换句话说,Matsumoto 总是想要设计一种语言。他会用 Perl 和 Python,但他想要一种,依他之见,“比 Perl 更强大,比 Python 更面向对象”的语言。他还声明,他想要让 Ruby 成为令程序设计简单有趣的语言。有些人说 Ruby 做了您期望它做的事情。您通常可以猜出您想要的命令的正确语法。这称为最少意外原则(Principle of Least Surprise)。

Ruby 是纯面向对象语言。我的意思是,Ruby 中的所有东西都是对象。在二十多年后的现在,我理解了为什么 Smalltalk 程序设计团队如此热心于 Smalltalk。我从没能够真正理解 Smalltalk,大部分因为它非常神秘的语法(依我的看法)。然而,Ruby 像是对于凡人的 Smalltalk。现在,我“了解”了它,并且它很有趣。

对于许多人来说,Ruby 已经取代 Perl 和 Python,作为编写快速脚本的语言。但是,Ruby 最近用于实现完整的应用程序,并且结合 Rails Web 应用程序框架,许多组织采用它作为基于 Web 的应用程序的语言选择。 6

一些共同特性

所有语言都必须拥有某种控制计算流和表示数据的能力。所有这三种语言都有类似的能力。它们支持许多不同的类型(整型和浮点型)、字符、字符串等等。它们还有一些内嵌的数据结构,像数组、哈希表和对像记录一样的结构的某种支持。

许多熟悉像 C 的语言的程序设计人员可能不了解哈希表,它也称作字典或相联数组。哈希表是类似数组的结构,但是哈希表中的值是通过键,而不是数字位置来访问的。因此,如果您拥有一个包含了您班中所有学生期末成绩的称为 grades 的哈希表,那么您可以用如下的语句将 J. Doe 的成绩设置为 A:

grades['J. Doe'] = 'A'

哈希表在访问没有明显顺序的数据方面难以置信的有用。如果 Java 程序设计人员使用过任何 Map 类,那么他们就很熟悉哈希表。

这三种语言都支持正则表达式和模式匹配。许多系统管理任务都涉及处理系统文件、日志文件、配置文件等等中的文本。许多信息都表示为程序可以用正则表达式找出信息的格式。举例来说,如果您想要确定字符串 str 是否包含子串“IBM”,其后有“Rational”,但不必要紧挨着它,您可以创建如下的 Perl 小程序:

$str = 'This is a sentence containing IBM and Rational in it.';if ($str =~ /IBM.*Rational/) {    print "True"} else {    print "False"} 

在此,斜杠中间的字符形成了简单的模式。该模式中唯一特殊的字符是‘.*’,它表示任何类型的零个或多个字符的序列。上面的程序打印出“True”。对于大多数曾经使用过类 Unix 系统的程序设计人员来说应该熟悉正则表达式。它们可能十分神秘,但是值得学习,因为它们给予您文本处理的与生俱来的力量。

考虑中的每种语言都支持各种类型的输入和输出。他们还能格式化数据,这样可以生成简洁的报告。事实上,当 Perl 首次发布时,人们认为“Perl”代表 Practical Extraction and Report Language。事实上,名称“Perl”真的没有特殊的意思。它不是缩写。Wall 最初打算根据 Gospel of Matthew 的 Pearl of Great Price 的寓言将他的语言命名为 Pearl,但他发现另外一种语言叫 PEARL,然后决定将他的语言命名为 Perl。

评价差别

虽然在这三种语言中还存在许多我可以介绍的共同特性,但是我想要用余下的时间谈论一些令每种语言独特于其他两种的差别。我相信某些差别在给定语言中是优点,其他的差别是弱点。是的,我的一些反应纯粹是主观的,但其他是根据一些深思的推理。

代码可读性总是我的痛处。像 Guido van Rossum 一样,我在试图以清晰度为代价从我的程序中得到最后一点性能之上,重视读和理解代码的能力。当然,有一些情况下,您只是不得不让您的程序尽可能最优,但是我只有在确定其必要性之后才这样做。

我们正在考虑的所有语言都能够用可读的方式编写。然而,仅仅因为可以用那种方式编写它们不意味着它们是实际的。我之前提到过缩进 Python 代码的需要。由于我总是缩进我的代码,所以我没遇到这样的问题。然而,我会遇到像图 1 中我指出的可能出现的错误的问题。每种语言都拥有一种以上支持用它进行程序的编写的集成开发环境(integrated development environment,IDE)。Eclipse 平台拥有支持它们的插件。当您拥有 IDE 或语法敏感的编辑器时,像缩进的问题对于您来说常常是要注意的。

至于可读性,依我看来,Python 赢得了首位,而 Perl 是失败者。我经常把 Perl 分类为“只写”语言。Perl 程序设计人员已经采用了这样的文化,开发最紧凑,简洁的代码是十分有价值的。通常,这是以可读性为代价的。由于使用了 Perl 中的 special variables(特殊变量),就产生了这个问题。一旦您习惯于 Perl 术语,您就会知道许多特殊变量,但对于一些正在学 Perl 的人来说,试图读 Perl 程序就像试图解密象形文字。Perl 语言,在展示它们如何能够利用非常酷且不明显的代码做出真正简洁的事情方面,给予技术人员很大的欢乐。图 2 中的脚本展示出一个相当小段的 Perl 代码能够有多神秘。 7

1 #!/usr/local/bin/perl2 $op = shift;3 for (@ARGV) {4     $was = $_;5     eval $op;6     die $@ if $@;7     rename($was,$_) unless $was eq $_;8 }

图 2:八行 Perl 代码

好的,让我们看看图 2,考虑一些可能对非 Perl 程序设计人员不明显的东西。第 2 行将第一个词从对脚本的参数中移去,并将其放入标量变量 op 中。我们知道,值正在被从参数表中移去,因为 shift 方法中没有给出参数。当这发生在脚本的最外层时,它自动假设参数表是参数。我们还知道 op 是标量变量,因为它的前缀是 $。如果它的前缀是 @,那么它将是数组,而如果它的前缀是 %,那么它将是哈希表。

第 3 到第 8 行是一个循环。该循环在数组 ARGV 上迭代,它是在命令行传递给脚本的余下的参数集合。第 4 行将变量 was 设置为参数表中下一项的值。$_ 是另一个在不同上下文中有不同含义的特殊变量。

第 5 和第 6 行也许是最令人迷惑的。第 5 行使用 eval 语句,它是动态语言的最强大的特性之一。它允许您执行您读入的或创建的文本,就好像它是您程序的一部分。什么是要执行的?它是第一个我们移到 op 中的参数。如果您不理解该脚本的目的,您将会很难理解将要发生什么。该行指示,第一个参数应该为可以应用于一个值的某个 Perl 表达式 —— 我们移到 was 中的参数(仍旧在 $_ 中)。期望值是某种正则表达式替代的表达式,像 s/txt/pl/,它在操作数中用“pl”替代“txt”的出现位置。当您根据操作数求值时,它将在操作数中的适当位置进行变更。

如果在求值时出现错误,那么第 6 行将打印出错误消息(存储在特殊变量 $@ 中)并使脚本退出。

这是仅用一些行代码所写的非常强大的脚本。同样的,在 Linux 系统中,您可以输入以下命令行(假设您的脚本名称为 rename.pl,并且是可执行的)来变更一组文件的名称的年份部分:

rename.pl s/2006/2007/ HW1-2006.txt HW2-2006.txt HW3-2006.txt

利用一些普通的正则表达式和其他的 Linux 命令,您可以将它们的输出重定向为一列文件名。

在此,我已经花了许多时间在 Perl 上。我想要例举它拥有的能力,以及您在阅读它时可能遇到的困难。而不想让您认为我完全地避开 Perl。我用 Perl 编写了我的硕士论文的所有代码。它是处理文本,并生成其他语言,像 C 和 C++,源代码的伟大语言。虽然我认为现在我更满意 Ruby,但是我今天可能仍旧为了该目的而选择 Perl。

Python 拥有 Perl 的大多数特性,但添加了许多肯定令希望面向对象的程序设计人员高兴的更先进的特性,您可以写程序来处理程序的反省(像 Java 中的反射),lambda(匿名)方法,等等。如果您是个喜欢使用所有不同的程序设计范型的语言专家,那么 Python 是个很好的选择。它还得到许多提供能够处理先进技术(像不同的音频文件格式、视频文件,和许多其他的)的模块和库的支持。Python 还拥有许多扩展,像 Pyro,它是支持机器人技术探索的开发平台。 8

图 3 展示了类似于上面的 Perl 程序的,用 Python 编写的程序。它根据某种正则表达式匹配,在目录中将文件重命名。 9

1 #!/usr/local/bin/python 2 3 # Python Rename File 1.0  4 # Author: Douglas Palovick 5 # License: GPL http://www.gnu.org/licenses/gpl.txt 6 7 import re, os 8 rxin = raw_input('enter a regex to search for:/n') 9 foo = re.compile(rxin)10 newname = raw_input('enter a new base name:/n')11 a = 012 for fname in os.listdir(os.getcwd()):13     allowed_name = re.compile(rxin).match14     if allowed_name(fname):15         # newfname = string.lower(re.sub(foo,16                                    # '', fname))17         # b = (newname + str(a))18         a += 119         c = os.path.splitext(fname)20         b = (newname + str(a) + c[1])21         os.rename(fname, b)

图 3:用 Python 写的程序

此程序不像 Perl 版本那样简洁,且没有特殊变量和假设。我有一些想要指出的部分。注意第 9 行。它使用正则表达式模块(re),由用户在第 8 行输入的正则表达式创建已编译的正则表达式。Java 程序设计人员会看到它与 Java Matcher 类的相似之处。我将把余下的代码留给读者来分析。





回页首


结束语

上面的实例没有真正地表现出 Perl 或 Python 的真正实力,这也不是我在这篇短文中的意图。但我希望这些实例和比较能够吊起您寻找更多内容的胃口。我将留到下个月探讨 Ruby 及 Rails 框架。

在离开本月的专栏之前,我想要指出,本文有一个附带的 归档的 zip 文件。它包含四个文件。一个是描述了我给三个当前和以前学生的问题的 PDF 文件。他们每个人都有至少一种语言的经验。我让他们用一种我指定的动态语言编写程序解决该问题。伴随该说明书的三个文件是他们的解决方案。看看它们,并考虑您喜欢哪些。

致谢

我想要感谢三个学生程序员花时间编写了作为本文附录的程序。Tyler Boone 编写 Perl 程序,Tom Rybka 编写 Python 程序,而 Jim Schementi 编写 Ruby 实现。

注释

1 1994 年,我参加了 USENIX Symposium on Very High Level Languages,会上提出的许多论文讨论了动态语言。

2 http://www.tiobe.com/。这些 Web 页将提供关于如何计算索引的信息,以及关于它们的等级鉴定系统的详细情况。

3我在这里非常概括地使用的 software community(软件团体)包括想要尝试写程序的任何人。

4 您可以在 Wikipedia 的动态语言页面上找到许多关于这些和其他动态语言的信息的链接:http://en.wikipedia.org/wiki/Dynamic_language

5 Programming Ruby 第一版的序言,第二版中重复印刷,Pragmatic Programmers,2004 年,ISBN0974514055。

6 下个月我们将更详细地探讨 Ruby on Rails。

7 此脚本,称为 rename,可以在 http://user.it.uu.se/~matkin/programming/PERL/ 找到。Web 页面上说它是由 Larry Wall,Perl 的创建者所写的。

8 参见 http://pyrorobotics.org/,了解关于 Pyro 的信息。

9 此程序可以在 http://www.palovick.com/code/python/python-rename-files.php 找到。



参考资料

学习

讨论
  • 现在开办了一个特别为 Rational Edge 的文章创办的 新论坛,现在您就可以分享您对本文或本期杂志或以前杂志中的其他文章的想法。阅读世界各地您的同行们所说的内容,生成您自己的讨论,或者加入正在进行的讨论。单击 这里 开始。

  • 全球 Rational 用户组社区


关于作者

Author photo

Gary Pollice 是麻省 Worcester 市 Worcester Polytechnic Institute 的一名实践教授。他教授软件工程、设计、测试以及其它计算机科学的课程,同时也指导学生项目。在进入学术界之前,他从事了 35 年多的软件开发,开发过各种软件,包括商业应用到编译器和工具等等。他在行业内的最后一份工作是在 IBM Rational 软件,他是有名的“RUP 倔老头”,同时也是最早的 Rational Suite 团队成员之一。 他是《小型团队软件开发:以 RUP 为中心的方法》(Software Development for Small Teams: A RUP-Centric Approach)一书的主要作者,该书由 Addison-Wesley 于 2004 年出版。他拥有数学专业文学学士学位,以及计算机科学理学硕士学位。