【30天Lua重拾笔记28】进阶议题: Meta Programming

同步发表於个人网站

Meta Programming / 元程序设计

元程序设计(英语: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 / 元表

metatable是Lua对於table一个特殊的属性。每个table都可以设定另一个table作为其metatablemetatable可以设定/改变该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 打造社群网站|Day22 修改会员名称

连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...

[Day 25 - Modern CSS] 指定CSS作用域,模组化开发 CSS Modules

要在 React 中撰写 CSS,为元素添加样式,除了 Styled-components,另外还有...

GCP Container Registry

Container Registry 今天说一下如何在GCP上建立Docker Image私仓(Do...

Day 16 - CSS Text Shadow Mouse Move Effect

前言 JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用...

【後转前要多久】# Day13 CSS - Display: Flex (vs Float)

当没有任何CSS时, HTML预设显示区块元素(block)方式都是 往下一行一行(row)长 HT...