Lua

Install

$ brew install lua
$ brew install lua@5.1

OpenResty

$ brew install openresty

Luarocks

为 Lua 5.1 安装依赖并指定安装位置:

$ luarocks --lua-dir=/usr/local/opt/lua@5.1 --tree=/usr/local/ install lua-resty-balancer

如此会将 .lua 库安装到 /usr/local/share/lua/5.1/ 目录,将 .so 库安装到 /usr/local/lib/lua/5.1/ 目录。

local Account = {balance = 0}

function Account.deposit(value)
  Account.balance = Account.balance + value
end

function Account.withdraw(value)
  if value > Account.balance then
	error("insufficient funds", 2)
  end 
  Account.balance = Account.balance - value
end

Account.deposit(100)
Account.withdraw(10)
print(Account.balance)
-- Output:
-- 90

a = Account
Account = nil 
print(a.balance) -- 100
print(a.deposit) -- function: 0x7f9eedc05440
a.deposit(20) -- table.lua:4: attempt to index a nil value (upvalue 'Account')

方法

local Account = {balance = 0}

function Account.deposit(self, value)
  self.balance = self.balance + value
end

function Account:withdraw(value)
  if value > self.balance then
	error("insufficient funds", 2)
  end 
  self.balance = self.balance - value
end

Account.deposit(Account, 100)
Account:withdraw(10)
print(Account.balance)
-- Output:
-- 90

a = {balance = 0, deposit = Account.deposit, withdraw = Account.withdraw}
a.deposit(a, 100)
a.withdraw(a, 20) 
print(a.balance)
-- Output:
-- 80

元表

元表可以修改一个值在面对未知操作时的行为。例如,假设 a 和 b 都是表,那么可以通过元表定义 Lua 语言如何计算表达式 a + b。当 Lua 语言试图将两个表相加时,它会先检查两者之一是否有元表(metatable)且该元表中是否有 __add 字段。如果找到了该字段,就调用该字段的值,即所谓的元方法(metamethod)(是一个函数),在本例中就是用于计算表的和的函数。

__index 元方法

当访问一个表中不存在的字段时会得到 nil。这是正确的,但不是完整的真相。实际上,这些访问还会引发解释器查找元表中一个名为 __index 的元方法。如果没有这个元方法(在元表上定义),那么像一般情况下一样,结果即是 nil;否则,则由这个元方法来提供最终结果。

prototype = {x = 0, y = 0, width = 100, height = 100}

local mt = {}

function new(o)
	o = o or {}
	setmetatable(o, mt) 
	return o
end

mt.__index = function(_, key)
	return prototype[key]
end

w = new({x = 10, y = 20})
print(w.width)
-- Output:
-- 100

Lua 语言会发现 w 中没有对应的字段 width,但却有一个带有 __index 元方法的元表。因此,Lua 语言会以 w(表) 和 width(不存在的键)为参数来调用这个方法。元方法随后会用这个键检索原型并返回结果。

虽然叫做方法,但元方法 __index 不一定必须是一个函数,它还可以是一个表。当元方法是一个表时,Lua 语言就访问这个表。因此,在此前的示例中,可以把 __index 简单地声明为如下样式:

mt.__index = prototype

将一个表用作 __index 元方法为实现继承提供了一种简单快捷的方法。虽然将函数用作元方法开销更昂贵,但函数却更灵活:我们可以通过函数实现多继承、缓存以及其它一些变体。

能不能略过元表 mt 而直接设置 o.__index = prototype,答案是不能。

没有 setmetatable 的配合,仅仅指定 __index 元方法并不起作用,更重要的是也不会屏蔽对表自身的成员访问:

prototype = {x = 0, y = 0, width = 100, height = 100}

function new(o)
	o = o or {}
	--setmetatable(o, mt)
	o.__index = prototype
	return o
end

w = new({x = 10, y = 20})
print(w.x, w.y, w.width)
-- Output:
-- 10  20  nil

省略 __index 元方法的设置,仅仅 setmetatable(o, prototype) 也并不会自动访问 prototype 表的成员:

prototype = {x = 0, y = 0, width = 100, height = 100}

function new(o)
	o = o or {}
	setmetatable(o, prototype)
	--o.__index = prototype
	return o
end

w = new({x = 10, y = 20})
print(w.x, w.y, w.width)
-- Output:
-- 10  20  nil

可见,__index 需要和 setmetatable 配合使用才会起作用。

local mt = {__index = Account}

function Account.new(o)
	o = o or {}
	setmetatable(o, mt) 
	return o
end

a2 = Account.new({balance = 0}) 
a2:deposit(100) -- 相当于 getmetatable(a2).__index.deposit(a2, 100)
a2:withdraw(30)
print(a2.balance)
-- Output:
-- 70

继承

local Account = {
	balance = 0
}

function Account:withdraw(value)
	if value > self.balance then
		error("insufficient funds", 2)
	end
	self.balance = self.balance - value
end

function Account:deposit(value)
	self.balance = self.balance + value
end

-- 注意这里是为返回的新表 o 设置元表到 Account 表,而不是为 Account 表设置元表。
-- 更确切的表述是,为表 o 设置元表到 self,即 new 方法的 receiver 表,如此一来可取消依赖某个特定的元表,这是实现继承的最关键一点。
function Account:new(o)
	o = o or {}
	self.__index = self
	setmetatable(o, self)
	return o
end

local SpecialAccount = Account:new()

function SpecialAccount:getLimit()
	return self.limit or 0
end

function SpecialAccount:withdraw(value)
	if value > self.balance + self:getLimit() then -- not self.getLimit()
		error("insufficient funds", 2)
	end
	self.balance = self.balance - value
end

-- 继承的效果是,s 的元表是 SpecialAccount 表,
-- 而 SpecialAccount 的元表是 Account 表。
local s = SpecialAccount:new({limit = 100})

-- override SpecialAccount:getLimit()
function s:getLimit()
	return self.balance * 0.10
end

s:deposit(100)
s:withdraw(110)
print(s.balance)
-- Output:
-- -10

a = Account:new()
a:deposit(10)
print(a.balance)
-- Output:
-- 10
a:withdraw(100) -- ERROR: insufficient funds

注:以上代码的效果是 s 继承自 SpecialAccount,而 SpecialAccount 又继承自 Account