加载中…
正文 字体大小:

一种可扩展语言的演变:Lua的历史(翻译)

(2008-03-26 11:45:02)
标签:

lua

原文地址:http://www.lua.org/history.html

摘要:从它的起初,1993年,Lua编程语言的发展大大超过了我们的预期。在这篇文章中, 我们将描述Lua的发展轨迹,从它的作为2个项目的内部语言被创建,到2000年发布的Lua 4.0。我们讨论它一些概念的演变和实现的一些主要里程碑。

介绍

有 一个老笑话,说:“骆驼是委员会(committee)设计的马。”在编程语言的人中,这个笑话差不多和一个传言“编程语言由委员会们设计的”一样流行的。这 个传言被语言象Algol 68, PL/I, 和Ada证明了。它们都是由委员会们设计的, 但并不满足他们赞助者的要求。

然而,除了委员会们,有另外一套理论来解释这些语言的部分失败:所有它们创建出来的时候就很庞大。它们遵照自顶向下的设计过程,在编程者使用之前,这种语言已经被详细描述和定义了,甚至在任何编译器建立之前。

很多成功的语言,从另一个角度说,维护胜于设计。它们按照自底向上的过程,作为一种小巧的语言开始,通常只是为了适中的目标。随着人们开始使用这种语言, 设计缺陷浮现,新的特性被增加(或者, 最终, 被删除),争议点被明确化(或者,最终,模糊化)。这些语言演变的过程,是学习编程语言很重要的主题。比如,SIGPLAN 在编程语言的历史上已经赞助了两场学术讨论会。

在这篇文章,我们报告Lua编程语言的历史。从起初,它作为2个特殊项目的内部语言,Lua的发展已经远超过我们的想象。我们想成功的主要原因在于我们开始的设计决策:保持语言简单和小巧;保持其实现简单,小巧,快速,容易移植,和免费。

Lua由一个委员会设计(或者更确切的说是,维护);相当小,只有3个成员的委员会。事后,我们认识到由一个小的委员会来维护对这种语言是很正面的。一项新的特性,我们只有在达成一致才增加;否则,它将留到将来。以后增加一项特性远比以后要删除它容易得多。这个开发流程对保持语言的简单很重要。而简单是我们最重要的资产。大部分Lua的质量--速度,小巧,和容易移植--都来源于它的简单。

自从Lua的第一个版本有了它“真正”的用户,这些用户比我们贡献更大。他们总是通过建议,抱怨,使用报告和提问等方式,对这种语言做出非常重要的贡献。当然,我们这个小委员会也起了很重要的作用。它的结构给我们足够的惰性,以至我们可以倾听用户的意见,而不用跟随他们所有的建议。

我们将按年代排序组织下面的内容。从1993年,第一个导致Lua的创建的经历,延续到以后8年的议论,决议,工作,和乐趣。

开端

我们第一个在TeCGraf内部语言设计的经验是的一个数据入口(data-entry)的应用。PETROBRAS(巴西的石油公司)的工程师们需要准备输入数据文件给模拟器,一天要几次。这过程很枯燥而且容易出错,因为这仿真器程序遗留的代码需要严格的格式化的输入文件--典型的一列列赤裸裸的数字,没有任何提示每个数字是什么意思,当然每个数字有其特定的意思。工程师们可以抓取看一下,他们就可以看到特殊仿真器的图表。PETROBRAS要求TeCGraf创建几个图形前端这种类型的数据入口。这些数字可以交互式的输入,只需点击相关部分的图表 -- 比编辑那一列列的数字容易并且可读性强多了。此外,它还可以从输入数据中增加数据验证和计算来源的数量,减少用户处理的数据量,增加整个过程的可靠性。

为了在TeCGraf中简化开发这些前端,我们决定使用一致的方法编写他们,所以我们设计了一种简单的声明语言去描述每个数据入口任务。下面是一部分用这种语言编写得典型程序,我们叫它为DEL(data entry language):
:e gasket "gasket properties"
mat s # material
m f 0 # factor m
y f 0 # settlement stress
t i 1 # facing type
:p
gasket.m>30
gasket.m<3000
gasket.y>335.8
gasket.y<2576.8
语句:e定义一个实体(在这个例子中,叫gasket),它有一些有默认值的字段。语句:p定义一些gasket值得限制,我们可以用来实现数据的验证。DEL语言还有一些语句用来描述数据怎样输入输出。

DEL中的一个实体基本上像形式语言中得一个结构体或一笔记录。不同的是它的名字也出现在图元文件中。图元文件包含了工程师做的一些数据的相应图表,就像我们上面所说的。

这种简单的语言被证明是成功的。在TeCGraf方面,因为它简化了开发。在用户方面,它简单的适合了数据入口的应用。很快用户开始需要增加DEL更强大的功能,比如布尔表达式去控制一个实体是否要激活,而DEL变得更强大。当他们开始想要条件控制和循环得时候,很明显我们需要的是一门真正的编程语言。

以此同时我们开始为PETROBRAS做另一个项目,叫做PGM,一个可配置的岩石特性报表生成器。就像名字建议那样,这个报表生成器程序是高度可配置的。用户可以创建和定位轨道,选择颜色,字体,和文本,每个轨道可以有一个格子,也有它的一些选项集(日志/线性, 垂直和水平转动等等);每一条曲线有它的大小,溢出的时候可以被自动改变,等等。

所有这些配置可以被最终用户(典型的地质家或者工程师)修改。而程序要运行在一台小机器上,就像一台PC运行MS-DOS。我们决定最好的方法去配置这个应用是通过一门特殊的描述语言,我们叫它Sol:Simple Object Language第一个字母的简写,在葡萄牙语中是SUN的意思。

因为这个报表生成器有很多不同的对象,每个有不同的属性,我们不能在语言中固定那些对象和属性。取而代之的是,这种语言允许类型声明。解释器的主要任务是去读取这些描述,检查所给的对象和属性是否被正确定义,然后传达这些信息给主程序。为了允许主程序和解释器通讯,解释器被实现为一个C库,链接到主程序中。因此,主程序可以通过这个库的API访问所有的配置信息。此外,这个程序可以为每个类型注册一个回调函数,以致解释器可以在创建所给类型的对象的时候调用这个函数。

下面是一段典型的Sol代码段:
- defines a type `track', with numeric attributes `x' and `y',
- plus an untyped attribute `z'. `y' and `z' have default values.
type @track { x:number,y:number= 23, z=0}

- defines a type `line', with attributes `t' (a track),
- and `z', a list of numbers.
- `t' has as default value a track with x=8, y=23, and z=0.
type @line { t:@track=@track{x=8},z:number*}
- creates an object `t1', of type `track'
t1 = @track { y = 9, x = 10, z="hi!"}
- creates a line `l', with t=@track{x=9, y=10},
- and z=[2,3,4] (a list)
l = @line { t= @track{x=t1.y, y=t1.x}, z=[2,3,4] }
Sol的语法受到BiBTex和UIL(User Interface Language)一种在Motif中描述用户界面的语言很大的影响。

在1993年3月我们完成了第一版Sol语言的实现,但我们没有发布它。到1993年中,我们意识到DEL和Sol应该可以被合并成一个更强大的语言。可视化岩石特性的程序很快需要支持过程化编程,为了可以创建更多的成熟的布局。另一方面,数据入口程序也需要描述的工具,为了用户界面的编程。

所以,我们决定我们需要一种真正的语言,可以赋值,控制结构,子程序,等等。这种语言应该同时提供数据描述工具,就像Sol。此外,很多潜在的用户不是专业的程序员,这种语言避免难懂的语言(和语义)。最后新语言的实现应该高度可移植。

可移植性的需求产生了一个主要的加强:那2个应用应该完全可以可移植,它们的语言也是。政府拥有的PETROBRAS不可能选择特定的硬件,以为它使用公众的钱去买硬件是有严格的规定的。PETROBRAS因此有一非常不同的计算机集合。所以在TeCGraf为PETROBRAS开发软件必须运行在他们拥有的每台机器上,包括PC DOS, windows(那是是3.1版本),Macintosh,和很多版本的Unix.

这样的话,我们可以修改一门已有的语言,来代替重新创建一门。主要的选择是Tcl 和,后面的,Forh 和Perl。Perl不是一门可扩展的语言。在1993年,Tcl和Perl只能运行在Unix平台。这3种都有难懂的语法,而且没有一种是提供数据描述很好的支持。所以,我们开始自己弄一门新的语言。

很快我们意识到,按照我们的意图,这种语言不需要类型声明。我们可以使用语言自己去写一个类型检查的子程序,提供这种语言基本的自反工具(比如运行时类型信息)。一个赋值比如:
t1 = @track {y = 9, x = 10, z="hi!"}
在Sol里是合法的,应该在新语言中也合法,但它有一个不同的意思:它通过给定的域,创建了一个对象(一个关联表),然后调用函数track去验证这个对象的合法性(或者提供它的默认值)。

因为这种新语言在Sol(太阳)的基础上改的,TeCGraf里的一个朋友建议命名为Lua(葡萄牙语里是月亮的意思),然后Lua就出世了。

Lua继承了Sol记录和列表创建的语法,但它统一实现为关联表(associative tables),记录使用字符串(字段名)作为索引,列表使用整数为索引。除了这些数据描述的功能外,Lua没有新的概念;我们需要的只是一个轻量级的通用语言。所以我们确立了一个很小的控制结构集合。借用了Modula的语法(while, if和repeat until)。从CLU我们借用了多值赋值和函数调用的多值返回(一个比输入-输出和引用参数更清晰的概念)。从C++我们使用了内部思想允许局部变量在需用的时候定义。

一个小(非常小)的创新是连接字符串的语法。因为这种语言允许字符串强制转化成数字,一个+符号是很含糊的,所以我们创建了..的语法执行的操作符。

有争议的是分号的使用。我们认为需要分号可能有点混淆有FORTRAN背景的工程师,但不允许它们又混淆那些C或者Pascal背景的。最后,我们确定一个可选的分号(一个典型的委员会解决方案)。

开始时,Lua语言有7种类型:数字(用浮点型实现),字符串,(关联的)表,nil(一种只用一种值的类型也叫nil),userdata(一种在Lua里,通用的C指针用来表述C结构体),Lua函数,和C函数。(经过8年的持续演变,唯一在Lua类型上的改变是统一了Lua函数类型和C函数类型为一个单独的函数类型)。为了保持这种语言的小巧,我们不包含boolean类型。像在Lisp, nil表示false,而任何其它值表示true。这是极少的我们有时会后悔的交易。

Lua也从Sol继承了实现为一个库的概念。这个实现遵从一个现在被极限编程支持的原则,“最简单的东西最容易运作起来。”(the simplest thing that could possibly work)。我们使用lex写扫描器,用yacc写语法分析器。语法分析器把程序转化成可以被一个简单的基于堆栈的解析程序运行的字节码形式。这种语言有一个非常小的预定义的库,所以它很容易用C增加新功能。

虽然这个简单的实现—或者说因为这样—Lua超出了我们的期望。PGM和数据入口(ED)项目使用Lua非常成功(PGM今天还在用)。很快,别的一些TeCGraf的项目也开始使用Lua了。

开始的一些年(1994-1996)

新用户们创造了新的需求。不奇怪,第一个Lua的需求是更好的性能。使用Lua作为的数据描述提出了一种典型的脚本语言不寻常的挑战。

当我们开始使用Lua时, 我们定义它的潜在用户使用它作为图元文件的支持语言。Lua数据描述的功能允许它可以作为一种图形格式。对比其它的可编程的元文件,Lua元文件有基于真正的过程语言的优势。比如VRML格式使用javascript为过程对象建模,导致两种不种类的代码(不清晰的序列),使用Lua,在一个场景的描述上结合程序化的对象是很自然的。当显性的保存时,程序化的代码片段可以把描述语句结合起来对复杂的对象建模。

数据入口(data-entry)程序(ED)是第一个使用Lua作为它的图元文件。一个图表有几千部分也不是不常见得,每部分用一个有几千项目得Lua 构造器描述,在一个上百KB得文件里。那意味着从一门编程语言的前景来说,Lua必须处理巨大的程序和巨大的表达式。而且,因为Lua在闲置的时候预编译 (一个JIT编译器),它也意味着Lua编译器必须非常快。第一个这个性能需求的牺牲者是lex,用一个手写的扫描器替换由lex生成的几乎提高了Lua 编译器2倍的速度。
我们还为构造器创建了新的操作码。一个列表构造器原始的代码,比如像
   @[30, 40, 50]
操作码为
   CREATETABLE
PUSHNUMBER 1 # index
PUSHNUMBER 30 # value
SETTABLE
PUSHNUMBER 2 # index
PUSHNUMBER 40 # value
SETTABLE
PUSHNUMBER 3 # index
PUSHNUMBER 50 # value
SETTABLE
用新的操作码,这些代码像这样:
   CREATETABLE
PUSHNUMBER 30 # value
PUSHNUMBER 40 # value
PUSHNUMBER 50 # value
SETTABLE 1 3 # set elements from index 1 to 3
对于一个很长的构造器,不可能在保存它们之前把所有的元素入栈;因此代码生成器发布了一个SETTABLE 指令从多少到多少,去释放堆栈。

(从那以后,我们总是试着去改进编译时间。今天,Lua编译一个30000个赋值语句的程序比Perl快6倍,比Python快8倍。)

我们发布了Lua的新版本,使用了那些优化,在1994年7月,命名为Lua 1.1。这个版本可以通过FTP下载。先前的版本我们没有对公众发布,我们命名为Lua 1.0。就是在那个时候,我们还发布了第一篇文章描述Lua。

Lua 1.1有严格的用户许可协议。对学术使用是免费的,但商业用途不是。(尽管这样的许可协议,它还是开放源码的。)但那个许可协议没有起作用。大多数竞争 者,如Perl和Tcl,都是免费的。此外,商业上的限制阻碍了学术上的使用,很多学术上的项目都有最终走向市场的计划。所以我们发布了另一个版本, Lua 2.1,作为自由软件发布。

Lua 版本2

Lua 2.1(1995年2月发布),带来很多重要的改变。其中的一个不是在语言本身,而在于语言开发的过程:我们决定我们会一直保持改进这门语言,甚至付出一些不兼容上一个版本的代价。

在2.1版本我们实际上就引入了和1.1版本很大的不兼容(但我们提供了一些工具帮助转换)。我们在构造器上丢弃了@ ,而统一使用了单一的括号来表示记录和列表。丢弃@是个轻微的改变,但它实际上改变了这门语言的感觉,而不只是外观上。

更重要的是,我们简单化了构造器的语法。在Lua 1.1,表达式
@track{x=1, y=10} 有一个特殊意义。在Lua 2.1,表达式track{x=1, y=10}只是track({x=1, y=10})语法上的美化,也就是创建一个新的表然后作为唯一个参数传给函数track

从一开始,我们就设计Lua作为一种可扩展的语言,也就是说C程序可以注册它们自己的函数给Lua透明的调用。这样,很容易用特定领域的原生的功能来扩展Lua,以便最终用户可以使用定制的语言满足它们的需要。

在2.1版本,我们引进了fallbacks的概念:用户定义的函数,当Lua不知道怎样去处理的时候调用。Lua然后成为一种可以用2种方式扩展的语言:扩展它的原生函数集和用fallbacks扩展它的语法。这就是为什么我们现在叫Lua--一种可扩展的扩展语言。

我们为算术,比较,字符串连接,表的访问,等定义了fallbacks。当用户设置,相对应的fallbacks函数就会被调用,尽管这些操作的操作数不是需要的类型。例如,不管什么时候2个值相加而它们之间的一个不是数字,一个fallback就会被调用,他的返回值作为相加的结果。

值得特别注意的—引入fallbacks真实得主要原因—是表访问的fallbacks:在语句x=a[i],如果a[i]是nil,一个fallback就会被调用(如果设置了)然后它的返回值作为a[i]的值。这个简单的新特性允许编程者去实现不同的语义访问表。特别是,可以实现几种类型的继承,最简单的由委托代理的单继承:
   function Index (a,i)
     if i == "parent" then       - to avoid loop
       return nil
     end
     local p = a.parent
     if type(p) == "table" then
       return p[i]               - may trigger Index again
     else
       return nil
     end
   end

   setfallback("index", Index)

这个代码跟随了一个向上的“parents”链,直到一张表有它需要的字段或者链结束。当“index”fallback被像上面那样被设置,下面的代码打印出red,尽管b没有color的字段:

   a=Window{x=100, y=200, color="red"}
   b=Window{x=300, y=400, parent=a}
   print(b.color)

关于通过一个parent字段得委任代理,没有魔数或者硬编码。这是编程者的选择。她可以使用一个不同的名字代替“parent”字段,或者实现更复杂的多重继承,通过允许“parent”字段自己本身是一个父类的表,然后按顺序访问,或者其它方法。

不同的fallbacks被调用当a不是一个表。有一个”gettable”fallback,在取a[i]的值的时候触发,如x=a[i];而一个“settable”fallback, 在设置a[i]的值的时候被触发,如a[i]=x。

有很多中利用这些表fallbacks的可能性;跨语言继承是非常强大的一个:当a是一个userdata值(一个在主C程序里面的指针),表fallbacks可以给编程者透明的访问这些留驻在主程序的数据结构的值。

我们不把那些可能的行为硬编码的决定形成了一个Lua的主要设计概念:meta-mechanisms。取代很多特性产生语言垃圾,我们提供很多方法使用户可以编写她们的特性,她们需要的方面,也她只需要的那些特性。

fallback meta-mechanism允许Lua支持面向对象编程,从这种意义上说(多种类型得)继承(或者操作符重载)可以被实现。我们甚至增加了一些句法上的美化定义和使用了“方法”:功能可以定义为a:f(x,y,z)而隐含的参数

(未完)

0

阅读 评论 收藏 转载 喜欢 打印举报
前一篇:找点事干
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

    < 前一篇找点事干
      

    新浪BLOG意见反馈留言板 电话:4006900000 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有