【问题标题】:Lua internals: how do string methods work as methods?Lua 内部结构:字符串方法如何作为方法工作?
【发布时间】:2021-04-09 17:36:28
【问题描述】:

Lua 有一个语法糖,它允许实现 OOP 方法:运算符 : 当用作 foo:bar(biz) 时等效于 foo.bar(foo,biz)。但是,我注意到标准字符串库也以这种方式积极使用它:

local a = "a normal string"
local b = a:reverse() -- ????????
print(b)
-- outputs: gnirts lamron a

问题是:这究竟是如何实现的?自然,当您尝试假设它扩展为a.reverse(a) 时,它就会崩溃,因为a 是一个字符串,不可能包含键reverse,而且,该语句实际上等价于string.reverse(a)。我的预感是它实际上扩展为_G[type(a)].reverse(a),但你记得同样的事情发生在文件描述符上的io 库中......

【问题讨论】:

  • a.reverse(a) 真的失败了吗?它适用于 tio.run:tio.run/…

标签: lua


【解决方案1】:

冒号语法实际上等同于点语法加上 self 参数。那么问题来了,a.reverse 是如何工作的?

The Lua 5.4 manual explains:

字符串库在表string 中提供了所有函数。它还为__index 字段指向string 表的字符串设置元表。因此,您可以使用面向对象风格的字符串函数。比如string.byte(s,i)可以写成s:byte(i)

我们可以通过代码看到这些元表细节:

local a = "a normal string"
strindex = getmetatable(a).__index
if type(strindex) == "table" then
  print "string has metatable entry __index:"
  for k,v in pairs(strindex) do
    local same = (v == string[k])
    print(k, type(v), "same as string."..k..":", same)
  end
end

local b = a.reverse(a) -- ????????
print(b)
-- outputs: gnirts lamron a

Try it online!: 输出是

string has metatable entry __index:
rep function    same as string.rep: true
byte    function    same as string.byte:    true
sub function    same as string.sub: true
char    function    same as string.char:    true
gmatch  function    same as string.gmatch:  true
format  function    same as string.format:  true
reverse function    same as string.reverse: true
match   function    same as string.match:   true
dump    function    same as string.dump:    true
len function    same as string.len: true
packsize    function    same as string.packsize:    true
find    function    same as string.find:    true
unpack  function    same as string.unpack:  true
upper   function    same as string.upper:   true
pack    function    same as string.pack:    true
lower   function    same as string.lower:   true
gsub    function    same as string.gsub:    true
gnirts lamron a

【讨论】:

  • 文字值可以有元表吗?这非常有趣。查看字符串库的 C 源代码,似乎比较 hacky,起初对我来说实际上没有意义,因此这个问题......创建一个虚拟字符串值并将其分配给虚拟字符串,然后在所有存在的字符串中持续存在。这实际上让我质疑元表是如何工作的!
  • 是的 - 你真的应该研究元表功能 - 这给 Lua 带来了魔力 - 我真的不能理解的一件事:为什么没有带有数学函数的 __index 数字? - 但不适合我 - 我将 em 设置在我需要它们的地方:debug.setmetatable(math.pi,{__index=math}) - 神奇的是:所有数字都有这个元方法
  • 它们的工作方式与 C API 函数 lua_getmetatablelua_setmetatable 和 Lua 库函数 getmetatablesetmetatable 以及指定用于检查元表的语言特性一样,对吧?这里奇怪的转折是,是的,像"word" 这样的文字表达式会自动获得一个元表。接下来要探索的是,如果自定义环境有不同的string 表或根本没有string 表,那会如何映射到特定文字表达式的值?
  • @aschepler - 编码员为函数执行 1% 的工作,为错误处理和异常执行 99% - 这就是这些函数存在的原因:assert、error、pcall、xpcall、warn 等. ;-)
【解决方案2】:

Lua (5.1 - 5.4) 中的每个字符串都有一个元方法为 __index 的元表。
那是所有字符串函数都存在的地方。
这是一个显示它的函数...

-- help()
help=function(help)
dump(debug.getmetatable(help).__index)
end

这很简单,不是吗?

编辑:因为我忘记了转储功能:-) ...

-- dump(table)
dump=function(...)
local args={...}
local test,dump=pcall(assert,args[1])
if test then
for key,value in pairs(dump) do
  io.write(string.format("%s=%s\n",key,value))
  io.flush()
end
  return true
else
  return test,dump
end
end

有一个叫做 _VERSION 的字符串 - 让我们看看它......

>help(_VERSION)
reverse = function: 0x565c4870
lower = function: 0x565c4ab0
sub = function: 0x565c7020
byte = function: 0x565c6d30
gsub = function: 0x565c7bb0
char = function: 0x565c4d80
format = function: 0x565c5060
unpack = function: 0x565c62d0
gmatch = function: 0x565c6ee0
dump = function: 0x565c5a90
upper = function: 0x565c47e0
find = function: 0x565c7ba0
len = function: 0x565c44d0
rep = function: 0x565c4900
pack = function: 0x565c66e0
packsize = function: 0x565c61c0
match = function: 0x565c7b90

这就是为什么它可以作为 .: 的方法使用的原因

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-17
    • 1970-01-01
    • 2012-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    相关资源
    最近更新 更多