同步發表於個人網站
元程式設計(英語: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
..
)