同步发表於个人网站
元程序设计(英语:Metaprogramming),又译超程序设计,是指某类电脑程序的编写,这类电脑程序编写或者操纵其它程序(或者自身)作为它们的资料,或者在执行时完成部分本应在编译时完成的工作。多数情况下,与手工编写全部代码相比,程序设计师可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译。 -- 维基百科
简单说,元程序设计,就是让「程序能够编写程序」,改变程序运行的部份行为。Lua本身具有部份如此的能力,举例来说,如果想要建立一组变数A-Z
,或许正常会这样子写:
A = 1
B = 2
C = 3
-- ....
Z = 26
但Lua可以有更聪明(hacking)的写法:
for i=65, 65+25 do -- ASCII Code of A is 65
print(string.char(i))
_ENV[string.char(i)] = i - 64 -- 1 to 26
end
还记得_ENV的作用吗?
不过今个儿没打算讨论这麽广泛的的元程序设计。会更专注於Lua本身提供的另一个特殊物件 -- metatable
/ 元表。
metatable
是Lua对於table
一个特殊的属性。每个table
都可以设定另一个table
作为其metatable
。metatable
可以设定/改变该table
的部份行为。
setmetatable
MetaPyStr = {}
PyStr = {"Hello, "}
setmetatable(PyStr, MetaPyStr)
getmetatable
要取的table
的元表也非常简单:
meta = getmetatable(PyStr)
table
行为metatable
是用於描述table
相关的物件,其可以设定/改变该table
的部份行为。举例来说,正常的table
使用tostring()
或是print()
得到的结果可能不那麽好理解:
print(PyStr)
--> table: 0x555de2908880
print(tostring(PyStr))
--> table: 0x555de2908880
可以透过设定元表的__tostring()
来改变输出样子:
MetaPyStr = {
__tostring = function(PyStr)
return '<PyStr "' .. PyStr[1] .. '">'
end
}
setmetatable(PyStr, MetaPyStr)
print(PyStr)
--> <PyStr "Hello, ">
跟Python很像对吧!
如果已经保有metatable
的存取,其实可以直接改写特性:
function MetaPyStr:__tostring()
return self[1]
end
print(PyStr)
--> "Hello, "
在之後还会看到弱表,其使用了
__mode
去描述table
的模式。
现在,想要实现Python字串的部份行为:
"Hello, " + "World" # => "Hello, World"
"Hello, " * 3 # => 'Hello, Hello, Hello, '
如果需要实现上面Python字串的行为,需要去改写加法运算(__add
)和乘法运算(__mul
)
不过在此之前,需要先改写一下PyStr
,让其是一个产生PyStr
物件的函数。而PyStr
物件是一个table
,其第一个值为初始化时输入的字串。
function PyStr(str)
assert(type(str) == 'string',
'str must is a string')
local _PyStr = {str}
setmetatable(_PyStr, MetaPyStr)
return _PyStr
end
print(PyStr("Hello, "))
--> Hello,
现在可以来正式实现加法运算与乘法运算:
function MetaPyStr:__add(other --[[PyStr]])
assert(type(other) == "table")
assert(type(other[1] == 'string'))
return self[1] .. other[1]
end
function MetaPyStr:__mul(times --[[integer]])
times =math.tointeger(times)
assert(times) -- check times is integer
local new_PyStr = {self[1]}
setmetatable(new_PyStr, MetaPyStr) -- set new_PyStr is Pystr
for _=2,times do
new_PyStr[1] = new_PyStr[1] .. self[1]
end
return new_PyStr
end
hello = PyStr("Hello, ")
world = PyStr("World")
print(hello + world)
--> Hello, World
print(hello * 3)
--> Hello, Hello, Hello,
可以让MetaPyStr
本身也可以建立PyStr
物件:
function MetaPyStr:new(str --[[string]])
assert(type(str) == 'string'
, 'str must is a string')
local _PyStr = {str}
setmetatable(_PyStr, self)
return _PyStr
end
hello = MetaPyStr:new("Hello, ")
print(hello)
--> Hello,
MetaPyStr:new
像不像Ruby的用法?
Ruby使用initialize
去初始话物件,使用class.new
去建立实体。
Lua也可以实现类似设计。有兴趣尝试模拟一下吧!
附带一题,
function MetaPyStr:new
这样的语法糖,直接为MetaPyStr
添加新的方法,是不是也很像Ruby的开放式类别(Open Classes)。
在此列出部份其他运算子的名称,你可以随意注册於metatable,改变物件行为:
__add
+
)__sub
-
)__mul
**
)__div
/
)__idiv
//
)__mod
%
)__eq
==
)__concat
..
)
<<: 寻觅 webpack - 27 - 真实世界的 webpack - 建立 webpack 生产环境 - 追踪建置
>>: 第27天:『SEO优化第九步』-优化页面网址和设定H1标签
连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...
要在 React 中撰写 CSS,为元素添加样式,除了 Styled-components,另外还有...
Container Registry 今天说一下如何在GCP上建立Docker Image私仓(Do...
前言 JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用...
当没有任何CSS时, HTML预设显示区块元素(block)方式都是 往下一行一行(row)长 HT...