返回介绍

3.5 Lua

发布于 2023-05-19 13:36:37 字数 19082 浏览 0 评论 0 收藏 0

Lua 是由巴西里约热内卢天主教大学的 Roberto Ierusalimschy 等人开发的一种编程语言。据我所知,诞生于南美洲,并在全世界范围内获得应用的编程语言,Lua 应该是第一个。当然,我不知道除了 Lua 之外还有没有其他语言是来自巴西的。

说句题外话,编程语言及其作者的国籍多种多样(表 1),大家可以看出,并不是所有的编程语言都是诞生于美国的,相反,貌似还是欧洲阵营更加强大一些。尤其是从人口比例来看的话,来自北欧的语言设计者比例相当高,而来自南美的只有 Lua,来自亚洲的则只有 Ruby,真是太寂寞了。

表1 编程语言及开发者的国籍

语 言

开 发 者

国 籍

Fortran

John Bacus

美国

C

Dennis Ritchie

美国

Pascal

Niklaus Wirth

瑞士

Simula

Kristen Nygaard

挪威

C++

Bjarne Stroustrup

丹麦

ML

Robin Milner

英国

Java

James Gosling

加拿大

Smalltalk

Alan Kay

美国

Perl

Larry Wall

美国

Python

Guido van Rossum

荷兰

PHP

Rasmus Lerdof

丹麦

Ruby

松本行弘

日本

Eiffel

Bertrand Meyer

法国

Erlang

Joe Armstrong

瑞典

Lua

Roberto Ierusalimschy

巴西

话说 Lua 这个词,在葡萄牙语中是“月亮”的意思。Lua 的特征是一种便于嵌入应用程序中的通用脚本语言。和它设计思想相似的语言还有 Tcl(Tool Command Language)。Tcl 的语言规格被控制在极小的规模,数据类型也只有字符串型一种,而 Lua 却具备了所有作为通用脚本语言的功能。

从实现上来说,Lua 的解释器是完全在 ANSI C 的范围内编写的,实现了极高的可移植性。另外,Lua 的高速虚拟机实现非常有名,在很多虚拟机系性能评分中都取得了优异的成绩。

示例程序

首先,图 1 是一个简单的 Lua 示例程序,这个程序可以计算阶乘。

--在Lua中以“--”开始的行为单行注释
--阶乘计算程序
function fact(n)
 if n == 1 then
  return 1
 else
  return n * fact(n -1)
 end
end

print(fact(6))
图 1 计算阶乘的 Lua 程序

怎么样?end 的用法等等是不是有点 Ruby 的感觉呢?说实话,我在写 Lua 程序的时候,经常会和 Ruby 搞混,从而在一些细节的差异上中招。例如,定义不是 def 而是 function、then 是不能省略的、调用函数时参数周围的括号是不能省略的,等等。

Lua 的语法虽然看起来有点像 Ruby,但其内涵更像 JavaScript。例如对象和散列表是不区分的、对象系统是使用函数对象的等。观察一下 Lua 的行为,就会觉得处处都有 JavaScript 的影子。

数据类型

作为通用脚本语言,Lua 可以操作下列数据类型:

· 数值型

· 字符串型

· 函数型

· 表型

· 用户自定义型

其中数值型和字符串型是在各种语言中都具备的普遍数据类型。值得注意的是,Lua 中的数值型全部是浮点数型,并没有整数型。在其他语言中(如果不特别声明的话),都是采用了和 Perl 相同的设计。函数和表我们稍后会逐一讲解。

用户自定义类型,是指用 C 语言等对 Lua 进行扩展时所使用的数据类型。文件输入输出等功能也是以用户自定义类型的方式来提供的。

函数

在 Lua 中,函数是和字符串、数值、表并列的基本数据结构。Lua 的函数属于第一类对象(first-class object),也就是说和数值等其他类型一样,可以赋值给变量、作为参数传递,以及作为返回值接收。

要获得作为值的函数,需要使用不指定名称的 function 语句。像图 2 中所示的指定名称的 function 语句,和用不指定名称的 function 语句所创建的函数进行赋值之后的结果是一样的。

-- 函数a的定义
function a(n)
 print(n)
end

-- 赋值给变量a
a(5)           --> 5
-- 通过type(a)获取a的数据类型
print(type(a)) --> function

-- 通过赋值语句定义函数
b = function (n)
 print(n)
end

-- 可以和a一样进行调用
b(5)           --> 5
-- 检查b的类型
print(type(b)) --> function
图 2 函数对象

在 Lua 中,通过最大限度利用其函数对象,就实现了面向对象编程等功能。关于用 Lua 实现面向对象编程的内容,我们稍后会进行讲解。

Lua 中最具特色的数据类型是表(table)。表是一种可以实现在其他语言中数组、散列表、对象所有这些功能的万能数据类型。JavaScript 中也是用散列表来实现对象的,但数组又是另外的实现方式,因此 Lua 这种将散列表和数组进行合并的做法显得更加彻底。像这样将数组和散列表合为一体的语言,除了 Lua 以外还有 PHP。

关于数组和散列表的合并到底是不是一个良好的选择,应该说还有讨论的余地,但至少在减少语言构成要素这一点上是有贡献的。由于 Lua 是动态类型语言,因此无论是变量还是数组的元素都可以存放任意类型的数据。

Lua 中各种表的使用方法如图 3 所示。需要注意的是,有一点和其他很多语言都不太一样,那就是作为数组的表索引是从 1 开始的。的确,以前的 FORTRAN 和 Pascal 中,数组的索引也是从 1 开始的,但从 C 语言之后,最近的语言中,索引基本上都是从 0 开始了,因此很容易搞错。特别是如果不小心添加了一个索引为 0 的元素时,这个元素就不是作为一个数组元素,而是作为一个键为 0 的散列表元素来建立的,这一点很容易中招。

--作为数组的表
array = {1, 2, 3}

 --Lua的数组索引是从1开始的
print(array[1])--> 1
--#是用来求长度的操作符(很像Perl)
print(#array) --> 3

--作为散列表的表
hash = {x = 1, y = 2, z = 3}
 --取出散列表元素
print(hash['x']) --> 1
 --取出散列表元素(结构体风格)
print(hash.y) --> 2

--数组和散列表的共存
 --通过赋值添加成员
array["x"] = 42
 --通过赋值添加成员(结构体风格)
array.y= 55
 --无论哪种方式都可以访问
print(array["x"]) --> 42
print(array.x) --> 42
 --散列表元素长度不包含在“长度”中
print(#array) --> 3
图 3 表编程

此外,还有一点比较违背直觉,那就是在 Lua 中对表应用获取长度的操作符“#”时,其“长度”只包括以(正)整数为索引的数组元素,而表中的散列表元素则不包含长度中,这一点是需要注意的。

元表

Lua 本来不是设计为一种面向对象的语言,因此其面向对象功能是通过元表(meta table)这样一种非常怪异的方式来实现的。Lua 中并不直接支持面向对象语言中常见的类、对象和方法,其中对象和类是通过表来实现,而方法是通过函数来实现的。

首先我们来讲讲元表到底是什么。在 Lua 中,表和用户自定义数据类型中有元表这样一种设定,这个设定通过 setmetatable() 函数来执行。对于表和用户自定义数据类型来说,在需要执行某项操作时,就会产生与该操作相对应的事件。针对各个事件所进行的处理是根据事件类型而决定的,但对于设定了元表的表和用户自定义类型来说,Lua 会参照元表来进行处理。

例如,在执行表元素引用的 index 事件中,处理逻辑如下。当执行 table[key] 表达式时,首先会确认 table 是实际的表,还是用户自定义数据等其他类型。

如果 table 为实际的表,则不通过元表直接取出与 key 相对应的元素(使用 rawget 函数)。如果表中存在该元素,则该元素即为该事件的执行结果。

如果 key 所对应的元素不存在,则取出 table 的元表,并从 table 的元表中取出 __index 这个元素。如果 __index 元素没有被创建,则返回 nil 结果,结束事件处理。

当 table 不是实际的表时,也会从 table 的元表中取出 __index 元素。如果 __index 元素没有被创建,则表示 table 不支持 index 事件,产生 错误。

如果 __index 元素的值为一个函数,则将 table 和 index 作为参数调用该函数。否则,则忽略 table,直接将该元素(这里假定它为 h)作为表来使用,并从中检索 key 所对应的元素。

--table[key]对于table[key]的事件处理
function gettable_event(table, key)
 local h
 if type(table) == "table" then
  local v = rawget(table, key)
  if v ~= nil then return v end
  h = metatable(table).__index
  if h == nil then return nil end
 else
  h = metatable(table).__index
  if h == nil then
   error(...)
  end
 end
 if type(h) == "function" then
  return (h(table, key)) --call the
handler
 else
  return h[key] --or repeat operation
on it
 end
end
图 4 index 事件处理

如果将上述逻辑用 Lua 编写出来,就是图 4 这样。基本上,对于任何事件,其处理都可以归结为下面的逻辑:

· 如果存在规定的操作则执行它。

· 否则,从元表中取出各事件所对应的“__”开头的元素,如果该元素为函数,则调用该函数。

· 如果该元素不为函数,则用该元素代替 table 来执行事件所对应的处理逻辑。

Lua 所处理的事件一览如表 2 所示。

表2 Lua的事件

事 件 名

说 明

备 注

add

加法(+)

以从左到右的顺序搜索元表

sub

减法(-)

同上

mul

乘法(*)

同上

div

除法(/)

同上

mod

求余(%)

同上

pow

幂(^)

同上

unm

单项减法(-)

concat

连接(..)

以从左到右的顺序搜索元表

len

求长度(#)

eq

比较(==)

数值、字符串则直接比较

lt

小于(<)

大于(>)则两侧对调

le

小于等于(<=)

如果没有 __le 则搜索 __lt

index

引用元素([])

x[“key”] 和 x.key 都会触发该事件

newindex

设定元素([]=)

同上

call

函数调用

方法调用的实现

说了这么多,元表到底该如何使用,到底能实现哪些面向对象编程,好像还是不明白呢。

面向对象编程的基本就是创建对象和调用方法。Lua 中,原则上表是作为对象来使用的,因此创建对象没有任何问题。关于调用方法,如果表的元素为函数对象的话,则可以直接调用。

Lua 中可以这样写:

obj.x(foo)
这表示从 obj 变量所存放的表中取出以 x 为键的值,并将该值视为函数进行调用。这样一来,看上去就和其他面向对象语言中的方法调用一模一样了。

不过,如果将这种方式作为方法调用来考虑的话,从面向对象来说还有几个问题。

首先,obj.x 这种调用方式,说到底只是将表 obj 的属性 x 这个函数对象取出来而已。而在大多数面向对象语言中,方法的实体是位于类中,而不是位于每个单独的对象中。在 JavaScript 等基于原型的语言中,是以原型对象来代替类进行方法的搜索,因此每个单独的对象也并不拥有方法的实体。

因此,在 Lua 中,为了实现这样的方法搜索机制,需要使用元表的 index 事件。像图 4 中说明的一样,只要在元表中设定 __index,当 key 不存在时就会利用 __index 来进行搜索。

于是,只要编写图 5 这样的程序,就可以将对象的方法搜索转发到其他的对象。

proto = {
 x = function() print("x") end
}o
bj= {}
setmetatable(obj, {__index = proto})
obj.x()
图 5 使用元表实现方法搜索

w 在这种情况下,proto 就变成了原型对象,当 obj 中不存在的属性被引用时,就会去搜索 proto。这样一来,类似 JavaScript 这样基于原型的面向对象编程的基础就完成了。

不过,这样还是有问题。通过方法搜索所得到的函数对象只是单纯的函数,而无法获得最初调用方法的表(接收器)相关的信息。于是,过程和数据就发生了分离,无法顺利实现面向对象的功能。

JavaScript 中,这一关于接收器的信息可以通过 this 来访问。此外,在 Python 中通过方法调用的形式所获得的并非单纯的函数对象,而是一个“方法对象”,当调用这个方法对象时,接收器会在内部作为第一参数附加在函数的调用过程中。

那么,Lua 中该怎么办呢? Lua 准备了一种支持方法调用的语法糖(syntax sugar,指以易读和易写为目的而引入的语法形式)。在 Lua 中,对方法的调用,不推荐使用单纯的元素访问形式,如:

obj.x()
而是推荐使用这样的形式:

obj:x()
这就是 Lua 中添加的语法糖,它表示

obj.x(obj)
的意思。也就是说,通过冒号记法调用的函数,其接收器会被作为第一参数添加进来。不过,表达式 obj 的求值只会进行一次,因此即便对 obj 有副作用,也只会发生一次。冒号记法的函数调用中,如果还有其他参数的话,会相应顺次向后移动一个位置。

也就是说,在 Python 中通过使用方法对象来实现的、将接收器添加为第一参数的函数调用,在 Lua 中是通过采用一种特殊的调用形式来实现的。这样的设计不包含复杂的机制,能够让人感受到 Lua 的简单哲学。

这个语法糖对方法定义也有效,在图 5 的程序中添加下面几行:

function base:y(x)
  print(self,x)
end
语法糖会解释为下面的代码:

base.y = function(self,x)
  print(self)
end
从而在 base 中定义了一个方法。

Lua 中进行方法调用特别需要注意的一点,就是用冒号记法定义的方法。在调用的时候也必须使用冒号记法来进行调用,否则,和 self 相当的参数就不会被添加进去,参数的顺序就会错乱,从而无意中成为产生错误的原因。尤其是在其他语言中,方法调用和属性获取大多都采用相同的圆点记法,因此很容易混淆。此外,在 Lua 中,传递给函数的参数个数有差异时并不会出错,因此即便看见错误信息也无法马上发现问题。我也因为在这个问题上中过招而感到很苦恼。

基于原型编程

通过刚才讲解的机制,我们了解了进行面向对象编程所必需的最低限度的功能。然而,说实话,仅有这些功能还不够好用,为了让它更加接近其他的语言,我们再来少许加工一下。

正如刚才讲过的,方法调用通过使用语法糖就可以毫无问题地完成了。而稍显不足的部分,则是对现有对象的扩展机制,也就是说,在像 Ruby 这样基于类的语言中,相当于类和继承的功能。

不过,考虑到 Lua 的对象机制,比起基于类来说,还是基于原型更加合适。于是,我给大家准备了支持基于原型的面向对象编程的一个简单的库(图 6)。这是我自己编写的一个工具,即使不详细了解 Lua 原始的机制,也可以实现面向对象。

--Object为所有对象的上级
Object = {}

--创建现有对象副本的方法
function Object:clone()
 --成为新对象的表
 local object = {}
 --复制表元素
 for k,vin pairs(self) do
  object[k] = v
end

 --设定元表
 --虽然名字叫clone但并不是复制而是向自身转发
 --为了将对类等的修改反映出来
 setmetatable(object, {__index = self)

 return object
end

--以某个对象为原型创建新的对象
--新对象通过initialize方法进行初始化
--允许类似基于类编程的用法
function Object:new(...)
 --成为新对象的表
 local object = {}

 --找不到的方法将搜索目标设定为self
 setmetatable(object, {__index = self})

 --和Ruby一样,初始化时调用initialize方法
 --(...)表示将所有参数原样传递
 object:initialize(...)

 return object
end

--实例初始化函数
function Object:initialize(...)
 --默认不进行任何操作
end

--为了本来不需要进行的类似类的操作而准备原型
Class = Object:new()
图 6 Lua 面向对象用工具

本来,在基于原型的面向对象编程中,在创建对象时,对于找不到的方法,其转发目标是指定为原型对象的。因此,例如编写一个对图表上的点进行操作的程序的话,只要创建一个具有代表性的点,然后根据需要,通过复制那个点来创建新的点就可以了。

然而,大多数人还是习惯基于类的面向对象编程,因此实际上并不会去创建“具有代表性的点”,而大多会创建一个原型专用的对象,并像类一样去使用它。这个库也考虑到了这一点,因此单独提供了两个方法,一个是从原型创建对象的 new 方法,另一个是创建对象副本的 clone 方法。

clone 方法会返回对象的副本,元表则是参照被复制的对象来进行设定的。这里我们并没有单纯去复制元表,理由是原始对象如果被修改,则需要将修改的部分在副本对象中反映出来。例如,对相当于类的对象中添加了方法的话,我们希望子类中也拥有新添加的方法。

另一方面,new 方法是从原型创建新的对象,并通过 initialize 方法进行初始化。这里调用 initialize 方法的方式,是参考了 Ruby 的设计。

这个工具的使用实例如图 7 所示,每一行程序实际的功能请参见注释。

-- 首先创建新的原型
-- 创建表示“点”的原型Point
Point = Class:new()

-- Point实例初始化方法
-- 设定坐标x和y
function Point:initialize(x, y)
  self.x = x
  self.y = y
end

-- 定义Point类的方法magnitude
-- 求与原点之间的距离
function Point:magnitude()
  return math.sqrt(self.x^2 + self.y^2)
end

-- 创建Point类的实例
-- x = 3, y = 4
p = Point:new(3,4)

-- 确认是否设定了成员
print("p.x = ", p.x) --> p.x = 3
print("p.y = ", p.y) --> p.x = 4

-- 计算magnitude()
-- 由勾股定理可求得结果为5
print(p:magnitude()) --> 5

-- 继承了Point的Point3D类的定义
-- 为了创建子类要对类进行clone操作
Point3D = Point:clone()

-- Point3D对象的初始化方法
-- 由于变成三维空间因此增加了z轴上的值
function Point3D:initialize(x, y, z)
  -- 调用超类的initialize进行初始化
  -- 必须要指定一个self有点不美观
  Point.initialize(self, x, y)
  -- Point3D类的扩展部分
  self.z = z
end

-- Point3D用的magnitude()方法
function Point3D:magnitude()
  return math.sqrt(self.x^2 + self.y^2 + self.z^2)
end

-- 创建Point3D实例
p3 = Point3D:new(1,2,3)

-- 属性检查
print("p3.x = ", p3.x) --> 1
print("p3.y = ", p3.y) --> 2
print("p3.z = ", p3.z) --> 3

-- 调用magnitude方法
print(p3:magnitude()) --> 3.7416573867739

-- 创建一个p3的副本
p4 = p3:clone()
-- 属性检查
print("p4.x = ", p4.x) --> 1
print("p4.y = ", p4.y) --> 2
print("p4.z = ", p4.z) --> 3

-- 调用magnitude方法的结果相同
print(p4:magnitude()) --> 3.7416573867739
图 7 面向对象工具的使用示例

怎么样?这个库既保留了基于原型的风格,又让习惯了基于类的人也能容易使用,我这有点王婆卖瓜自卖自夸的感觉呢。

和 Ruby 的比较(语言篇)

以嵌入为方针进行设计的 Lua,在默认状态下真是简洁得惊人。在标准状态下,除了基本的数据类型之外,其他一概没有。功能上也就是文件输入输出、字符串模板匹配、表操作、几个数学函数,再加上包加载这些而已。和一开始就提供了大量功能的 Ruby 相比,显得有些贫乏。

正如之前所讲过的,Lua 中虽然能够进行面向对象编程,但用元表来进行编程,仿佛感觉是把对象剖开看到五脏六腑一样。这和 Perl 早期的面向对象编程感觉差不多。

话虽如此,我觉得,虽然感觉有些不适,但从实用角度来说,实现面向对象编程还是没有问题的,虽然提供的功能很少,但也并非一无是处。对语言的基本功能上也有一些不满,例如没有异常处理功能等。不过考虑到它是一种应用程序扩展语言,这一点也并非不能妥协。

只能说,功能和大小是需要权衡的吧。不过,Lua 可以通过 C 语言方便地添加功能。默认的功能仅仅是用来表达逻辑,更多的功能只要根据嵌入的应用程序的需求进行添加就可以了。

同样是以嵌入为方针的 Tcl,由于其附带的 GUI 库 Tk 的完成度相当好,而且出乎作者意料的是,更多地不是用于嵌入应用程序,而是直接作为 GUI 语言来使用。不过 Lua 目前还是遵照着最初的目的,以嵌入为主要的应用领域。

嵌入式语言 Lua

Lua 作为嵌入式语言,观察一下它的实现,会发现最具特点的部分是关于解释器的数据都包括在一个名为 lua_State 的结构体中。通过这样的方式,在一个进程中就可以容纳多个解释器,例如为游戏的角色各自分配一个独立的解释器(只要不在意内存用量的话)也是完全可以做到的。

此外,在多线程环境中,通过为每个线程分配解释器,可以最大限度发挥多核的性能。

再有,Lua 的垃圾回收也很有讲究。在游戏之类对实时性要求很高的环境中,对不再使用的对象进行回收的垃圾回收机制一直是个难题。例如,在射击游戏中,如果由于垃圾回收使玩家有 1 秒钟无法操作自己的飞机,那游戏就变得没法玩了。在嵌入环境中,虽然处理性能非常重要,但响应速度更加重要。Lua 通过使用增量垃圾回收的算法,使得程序的暂停时间得到大大缩短。

以轻量、高速、响应快为特色的 Lua,被用于嵌入到各种应用程序中。例如,网络游戏《魔兽世界》(World of Warcraft)、《仙境传说》(Ragnarok Online),以及美国 Adobe Systems 公司开发的图像处理软件“Adobe Photoshop Lightroom”等。也许是出于这个原因,Adobe 对 Lua 的开发提供了赞助。

比较罕见的是,Yamaha 的路由器 RTX1200 中也嵌入了 Lua。以前,美国 Cisco Systems 公司的路由器中曾经嵌入了 Tcl,但 Lua 似乎成为最近的流行趋势了。

除了上述这些以外,嵌入了 Lua 的游戏、Web 服务器、工具软件等应用程序,已然不计其数。

和 Ruby 的比较(实现篇)

如果从嵌入到应用程序这一角度来比较一下 Ruby 和 Lua 的话,则不能否认 Ruby 处在稍许不利的地位。

Ruby 原本是作为独立通用语言,以追求功能和易用性为目标而设计的。将其嵌入到其他应用该程序中,虽说不是不可能,但并非十分擅长,尤其对于缺少用于表现解释器的结构体来说,是一个很大的缺陷。出于这个原因,在一个进程中只能容纳一个解释器。像 Lua 很容易做到的对多个解释器的协调和对线程的分配,在 Ruby 中就非常困难。

有一个叫做 MVM(Multiple VM)的补丁可以解决这个问题,不过要将它引入到标准实现中还需要一些时间。

当然,嵌入了 Ruby 的应用程序也并非一个都没有。例如 Enterbrain 公司发售的软件《RPG 制作大师》1中就嵌入了一种叫做 RGSS(RubyGame Scripting System)的 RPG 编程工具,其实体就是 Ruby 1.8。

1 RPG 制作大师(RPG Maker)是由 Enterbrain 公司发售的一款 RPG(角色扮演游戏)制作工具。从 1990 年开始,这个系列已经在不同的平台上推出了数十个版本,RGSS 的诞生和加入则是从 2004 年的“RPG 制作大师 XP”版本开始的。

此外,我还听到过一个比较古老的报告,就是英国的酒吧中放置的“World Cup Trivia”游戏机中,也嵌入了 Ruby。

语言的优劣并不是非黑即白的。在语言本身的功能以及易用性方面 Ruby 更加优秀,我对此有足够的信心。但如果涉及到嵌入到应用程序中,Lua 在实现方面的实力更强,这一点是不能否认的。

嵌入式 Ruby

不过,伴随计算机性能的提升,在嵌入式领域中,CPU 性能和内存容量等指标也比以前有了大幅度的改善。控制器等组件的性能已经相当于不久之前的 PC,即便是游戏机,其性能也已经和 PC 不相上下了,而可以作为电脑来使用的手机,其性能、容量也在不断增加。

其结果,就是所谓嵌入式领域中的软件,其复杂性也和以前不可同日而语。如今,软件的开发效率在嵌入式领域中也成为了一个不可回避的问题。因此,为了提高软件开发效率,其中一个有力的工具,就是采用高级的编程语言。

为了这个目的,预计能够在嵌入式领域中大展身手的新 Ruby 引擎开发项目,已经被采纳为日本经济产业省 2010 年度“地域创新研究开发事业”。这个项目的开发由具备丰富嵌入式经验的各界人士共同进行,核心部分的开发工作由我来完成。这个轻型 Ruby 基于 MIT 授权协议进行开源化,通过“mruby”这个名称便可以获得源代码,请大家访问 https://github.com/mruby/mruby

这个轻型 Ruby 并非用来替代现有的 Ruby,而是以嵌入式领域为中心,对现在的 Ruby(CRuby)所做的补充。就像 JRuby 是面向 JVM 平台对 Ruby 语言可能性所做的扩展一样,新的轻型 Ruby 将是面向嵌入式领域,对 Ruby 可能性所做的又一次扩展。

伴随着嵌入式计算机性能的提升,软件的复杂化也逐步推进,像 Lua 这样以小规模的引擎面向应用程序嵌入的语言,今后的舞台应该会越来越广阔。

“编程语言的新潮流”后记

在本章中,我们着重介绍了一些(在原稿写成的时候)比较新颖的语言。世界上到底有多少种编程语言,具体的数字没人知道。算上个人兴趣制作的,或者以学者撰写论文的一部分而开发的语言的话,恐怕真的是不计其数了吧。实际上,光我上学时所属的研究室,在几年的时间里就开发了三四种新语言。我记得自己的毕业论文也是和编程语言(不是 Ruby)的设计、实现相关的。

以这样的背景所诞生的语言,大部分会随着作者的毕业、工作,或者随着兴趣的减弱等各种理由,开发逐渐停滞,然后慢慢消亡。在这样“无数的尸体”中,才出现了凤毛麟角般的异类,它们变得广为人知,并得以长期存在下去。

这样诞生的语言,在应用的过程中,也在不断进化。这几年最具有开创性的,莫过于 v8 和 LuaJIT 的出现。

v8 是 Google Chrome 浏览器中搭载的 JavaScript 引擎,LuaJIT 是本章中介绍过的面向嵌入环境的脚本语言 Lua 的高速实现。这两者都是作为动态语言以拥有惊人的速度为特点,其速度甚至超越了静态类型语言所擅长的编译式引擎的性能。动态编程语言的实现者(包括我在内)一直以来都以“由于没有编译时能利用的类型信息,加上语言的性质是动态的,因此高速实现是很困难(约等于不可能)的”作为借口。而现实中超高速引擎已经出现了,也就再也谈不上什么“不可能”了。今后,以此为基础,大家又要开始新一轮的进步。实际上,各种浏览器的 JavaScript 性能在这几年间获得了飞跃性的提高,已然进入了一个大竞争的时代。

今后的语言,需要追求兼具动态语言的灵活性和编译式语言的高速性。本章中介绍的 Dart,其诞生的背景就在于此。是像 Dart 这样采用非强制性类型信息,还是像某种静态类型语言一样采用类型推导,这正是语言进化方向中有意思的地方。

我在这几年中也酝酿了关于语言的一些构思。例如像 Ruby 一样为动态语言,但局部变量无法再次赋值(单一赋值),提供的数据结构基本上不可改变(immutable)等。这些都是函数型语言(尤其是 Erlang)的特征,如果和 Ruby 这样的面向对象功能结合起来的话,我想是不是能形成一种容易调试、又容易发挥多核性能的语言呢?由于我自己忙于开发 Ruby,没有精力再着手开发新的语言,各位读者之中有没有人想要挑战一下呢?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文