《The Evolution of Lua》翻译 part2
Lua2 1990 年的时候,面向对象迈向巅峰,对于 Lua 没有面向对象的支持,我们受到了很大的压力。我们不想将 Lua 变成面向对象,因为我们不想 “ 修复 ” 一种编程范式(fix a programming paradigm)。特别是,我们不觉得 Lua 需要将对象和类作为基础语言概念,我们觉得可以透过 table 来实现(table 可以保存方法和数据,因为函数是第一类对象)。直到今天,Lua 也没有强加任何对象和类模型给用户,我们初心不变。很多用户建议和实现了面向对象模型;面向对象也是邮件列表里经常讨论的问题,我们觉得这是健康的。
另一方面,我们希望允许 Lua 可以面向对象编程。我们决定提供一套灵活的机制,让用户可以选择对应用来说合适的模型,而不是修复模型。1995 年 2 月发布的 Lua2.1,标志着这些灵活语义的问世,极大的增加了 Lua 的表达能力,从此,灵活语义就变成了 Lua 的标志。
灵活语义的一个目标是允许 table 作为对象和类的基础。为了实现这个目标,我们需要实现 table 的继承。另一个目标是将 userdata 变成应用数据的天然代理,可以作为函数参数而不只是一个句柄。我们希望能够索引 userdata,就好像他们只是一个 table,可供调用他们身上的方法。这让 Lua 可以更为自然的实现自己的主要设计目标:通过提供脚本访问到应用服务和数据,从而扩展应用。我们决定实现一套 fallback 机制,让 Lua 把未定义行为交给程序员处理,而不是直接在语言本身实现这些特性。
在 Lua2.1 的时候,我们提供了 fallback 机制,支持以下行为:table 索引,算术操作符,字符串拼接,顺序比较,函数调用。当这些操作应用到 “ 错误 ” 的类型上,对应的 fallback 就会被调用到,允许程序员决定 Lua 如何处理。table 索引 fallback 允许 userdata 和其它值类型表现的跟表一样。我们也定义了当 Key 不在 table 时的 fallback,从而实现多种形式的继承(通过委托)。为了完善面向对象编程,我们添加了两个语法糖:function a:foo(…) 就好比 function a.foo(self,…) 一样,以及 a:foo(…) 作为 a.foo(a, …) 的语法糖。在 6.8 节我们会讨论 fallback 的细节。
从 Lua1.0 开始,我们就提供了值类型的内省函数(introspective functions):type,可以用来获取 Lua 值的类型;next,可以用来遍历一个 table;以及 nextvar,可以遍历全局环境。(正如第四章所述,这是为了实现类似 SOL 的类型检查)为了应付用户对完整调试设施的强烈需求,1995 年 12 月发布的 Lua2.2 引入了一个 debug API 来获取运行中的函数信息。这个 API 为用户提供了以 C 语言编写自己的内省函数工具链的手段,比如编写自己的调试器和性能分析工具。debug API 刚开始的时候相当简洁:debug 库允许访问 Lua 的调用栈,访问当前执行的代码行行数,以及一个可以查找指定值的变量名的函数。根据 M.Sc. 的 Tomas Gorham 的工作,debug API 在 1996 年 5 月发布的 Lua2.4 版本里得到了完善,提供了函数访问局部变量,提供了钩子在行数变化和函数调用时触发。
因为 Lua 在 Tecgraf 的广泛使用,很多大型的图形源文件都是用 Lua 写的,作为图形编辑器的输出格式。加载这些源文件会随着文件大小变得越来越大和越来越复杂而变的越来越长。从第一版开始,Lua 就预编译所有程序到字节码,再执行字节码。大型代码文件的加载时间可以通过保存 bytecode 来缩减。这和处理图形源文件特别有关系。所以在 Lua 2.4 版本,我们引入了一个外部编译器 Luac,可以编译一个 Lua 文件为字节码并保存为二进制文件。这个二进制文件的格式经过精心选择,可以轻松加载同时体积小巧。通过 luac,程序员可以在运行期避免词法分析和代码生成,这些操作早期还是比较耗时的。除了更快的加载,luac 还允许离线的语法检查,以及随意的用户改动。很多产品(比如模拟人生和 Adobe 的 Lightroom)都是透过预编译格式发布 Lua 脚本的。
在 luac 的实现过程里,我们开始将 Lua 的核心重构成清晰的分离模块。于是,我们现在可以轻易移除词法解析的模块(词法解析器,语法解析器和代码生成器),这些部分占了大约 35% 的核心代码,剩下的部分可以加载预编译的 Lua 脚本。这种代码剪裁,对于在移动设备、机器人和感应器这些小设备里嵌入 Lua,是有显著意义的。
从第一版开始,Lua 就自带有一个库来进行字符串处理。这个库在 Lua2.4 之前功能有限。但是,随着 Lua 的成熟,Lua 需要进行更重量级的字符串处理。我们认为,沿革 Snobol,Icon,Awk 和 Perl 的传统,为 Lua 添加模式匹配是自然而然的。但是,我们不想将第三方的模式匹配引擎打包到 Lua 里面去,因为这些引擎通常都很大,我们也希望避开因为引入第三方库带来的代码版权问题。
1995 年的第二学期,作为 Roberto 指导下的学生项目,Milton Jonathan,Pedro Miller Rabinovitch,Pedro Willemsens 和 Vinicius Almendra 为 Lua 写出了一个模式匹配库。那个设计的经验引导我们写出了我们自己的模式匹配引擎。1996 年的 12 月,我们在 Lua2.5 中添加了两个函数:strfind(最早职能查找纯文本)和新的 gsub 函数(名字来源于 Awk)。gsub 函数可以用来全局替换符合指定模式的子串。它接受一个新的字符串或者一个函数作为参数,函数参数会在每次遇到匹配时调用,并预期该函数返回新子串以供替换。为了缩小实现的规模,我们没有支持完整的正则表达式。我们支持的模式包括字符类,重复,以及捕获(但是没有可选或组匹配)除了简洁性,这种模式匹配还十分强大,是 Lua 的一个强有力的补充。
那一年是 Lua 历史上的转折点,因为 Lua 获得了全球的曝光。在 1996 年 6 月,我们在《Software:Practice & Experience》杂志上发布了一篇 Lua 的文章,为 Lua 带来了外部的关注,至少是学术圈子的关注。在 1996 年的 12 月,Lua 2.5 刚刚发布后,杂志 Dr.Dobb’s Journal 也发表了 Lua 的文章。Dr.Dobb’s Journal 是一本面向程序员的流行刊物,那篇文章为 Lua 带去了软件工业界的关注。在那次发布之后,我们收到了很多消息,其中一条是 1997 年 1 月收到的,来自 Bret Mogilefsky,LucasArts 出品的冒险游戏 ——Grim Fandango 的首席程序员。Bret 告诉我们,他从 Dr.Dobb’s 上读到了 Lua,他们打算用 Lua 代替自己写的脚本语言。1998 年 10 月,Grim Fandango 发布,1999 年 5 月 Bret 告诉我们 “ 大量的游戏都是用 Lua 写的 ”。那个时候,Bret 参加了 GDC(Game Developers’ Conference, 游戏开发者会议)的有关游戏脚本的圆桌会议。他谈到了 Grim Fandango 应用 Lua 的成功经验。很多我们认识的开发者,都是在那次事件里认识到 Lua 的。在那以后,Lua 在游戏程序员之间口耳相传,变成了游戏工业里可资销售的技能。
因为 Lua 的国际化曝光,向我们提问 Lua 的信息越来越多。1997 年 2 月,我们建立了邮件列表,好更有效率的处理这些问题,让其他人也能帮忙回答问题,并开始建设 Lua 社区。这个列表至今发布了超过 38000 条消息。很多热门游戏对 Lua 的使用,吸引了很多人到邮件列表里。现在已经有超过 1200 个订阅者了。Lua 列表十分友善,同时又富有技术性,我们对此深感幸运。邮件列表逐渐变成了 Lua 社区的焦点所在,也是 Lua 演进的动力源泉。所有的重大事件,都是首先在邮件列表里出现的:发布重大通知,请求新特性,bug 报告等等。
在 Usenet 新闻组里建立 comp.lang.lua 讨论组曾经在邮件列表里讨论过两次,分别是 1998 年的 4 月和 1999 年的 7 月。两次的结论都是邮件列表的流量不能保证创建一个新闻组。而且,更多人倾向于邮件列表。随着多个阅读和搜索完整邮件存档的 web 界面问世,创建新闻组变得无关紧要了。