【问题标题】:Lua: When is it possible to use colon syntax?Lua:什么时候可以使用冒号语法?
【发布时间】:2016-01-08 05:27:17
【问题描述】:

虽然我了解基本的difference between . and :,但我还没有完全弄清楚 Lua 何时允许使用冒号语法。例如,这样的事情确实有效:

s = "test"
-- type(s) is string.
-- so I can write a colon function for that type
function string:myFunc() 
  return #self 
end

-- and colon function calls are possible
s:myFunc()

但是,相同的模式似乎不适用于其他类型。例如,当我有 table 而不是 string 时:

t = {}
-- type(t) is table.
-- so I can write a colon function for that type
function table:myFunc() 
  return #self 
end

-- Surprisingly, a colon function call is not not possible!
t:myFunc() -- error: attempt to call method 'myFunc' (a nil value)
-- But the verbose dot call works
table.myFunc(t)

转向另一种类型:

x = 1
-- type(x) is number.
-- So I was expecting that I can write a colon function 
-- for that type as well. However, in this case even this
-- fails: 
function number:myFunc() 
  return self 
end
-- error: attempt to index global 'number' (a nil value)

我目前正在尝试理解这一点。这样的结论对吗

  • string 等某些类型允许冒号函数定义冒号函数调用。
  • table 等其他类型仅允许冒号函数定义,但不允许冒号函数调用。
  • 而其他类型,例如 number,两者都不允许。

这些差异的确切原因是什么?是否有所有类型的列表,显示它们支持哪种类型的冒号语法? number 案例是否有解决方法,允许编写例如x:abs()?

【问题讨论】:

    标签: lua


    【解决方案1】:

    string 上的第一个示例有效,因为所有字符串共享同一个元表,并且存储在名为 string 的表中。

    来自string

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


    table 的第二个例子不起作用,因为每个表都有自己的元表,名为table 的表只是表库所有功能的集合。


    数字等类型默认不支持元表。

    【讨论】:

    • 所以string 是唯一允许这样做的类型?
    • 冒号语法是just a syntactic sugar。例如,表支持冒号语法,但是,所有表都没有一个通用的元表。
    • @bluenote10,文件句柄是另一个例子。
    【解决方案2】:

    作为一个Lua新手,我花了一段时间才理解@Yu Hao的回答,所以我会尝试为其他初学者添加一些细节。如有错误请指正。

    据我所知,x:someFunc() 之类的调用在 [*] 时有效:

    • x 有一个元表
    • 并且元表有一个字段__index
    • 指向一个包含函数someFunc的表。

    正如于浩所指出的,字符串会自动获得一个指向表string的元表,例如:

    th> s = 'test'
    
    th> getmetatable(s)
    {
      __mod : function: 0x40c3cd30
      __index : 
        {
          upper : function: builtin#82
          rep : function: builtin#79
          split : function: 0x40ffe888
          gfind : function: builtin#87
          find : function: builtin#84
          reverse : function: builtin#80
          lower : function: builtin#81
          len : function: 0x40af0b30
          tosymbol : function: 0x40ffe8a8
          myFunc : function: 0x41d82be0 -- note: this comes from our custom function string:myFunc()
          dump : function: builtin#83
          byte : function: builtin#76
          char : function: builtin#77
          gmatch : function: builtin#87
          match : function: builtin#85
          sub : function: builtin#78
          gsub : function: builtin#88
          format : function: builtin#89
        }
    }
    

    所以在这种情况下s:myFunc() 会自动工作。为了对table 使用冒号语法,我们可以手动设置它的元表:

    th> function enableColonForTable(t)
    ..>   meta = {__index = table}
    ..>   setmetatable(t, meta)
    ..> end
    
    th> t = {}
    
    th> enableColonForTable(t)
    
    th> t:insert(1) -- works now!
    

    另一个观察结果是,__index 是否指向与类型名称完全相同的表实际上并不重要。除了meta = {__index = table},我们还可以这样做:

    th> arbitraryScope = {}
    
    th> function arbitraryScope:test() return "something" end
    
    th> t = {}
    
    th> setmetatable(t, {__index = arbitraryScope})
    {}
    
    th> t:test()
    something   
    

    这也是number 的关键区别。虽然存在名为stringtable 的现有表,但没有名为number 的现有表。这就是为什么甚至定义例如function number:abs() 以前失败过。但我们仍然可以让它工作:

    th> number = {}
    
    th> function number:abs() return math.abs(self) end
    
    th> x = -123
    
    th> debug.setmetatable(x, {__index = number})
    -123    
    
    th> x:abs()
    123 
    

    请注意,我们必须在这里使用debug.setmetatable 而不是setmetatable。两者的区别似乎是setmetatable 只为给定实例设置元表,而debug.setmetatable 为整个类型设置元表。显然设置了一个individual metatable for numbers is forbidden(无论如何也没有多大意义)。这意味着(与表格相比)新构建的数字现在默认具有给定的元表,所以这是可行的:

    th> y = -42
    
    th> y:abs()
    42  
    

    [*] 更新

    正如 Tom Blodget 所指出的,如果 x 本身用作命名空间,x:someFunc() 也可以工作,即它是一个带有方法字段 someFunc 的表。例如你可以做table:insert(1)。但现在命名空间(名为table 的表)作为self 传递,您将向命名空间添加数据:

    th> print(getmetatable(table)) -- note: "table" does not have a metatable
    nil 
    
    th> table:insert(1) -- yet a colon syntax call works
    
    th> table
    {
      prune : function: 0x4156bde0
      getn : function: 0x41eb0720
      maxn : function: builtin#90
      remove : function: 0x41eb08c8
      foreachi : function: 0x41eb05b8
      sort : function: builtin#93
      concat : function: builtin#92
      unpack : function: builtin#16
      splice : function: 0x4156bdc0
      foreach : function: 0x41eb0688
      1 : 1
      pack : function: builtin#94
      insert : function: builtin#91
    }
    

    【讨论】:

    • 很好的重述,但您错过了明显的:表字段可以作为方法调用。在我的answer 中查看背景信息。
    【解决方案3】:

    补充答案:

    首先,请注意函数是一个值(又名"first-class citizen")。

    : 是 Lua 中的三个索引运算符之一。索引运算符从可以索引的对象(无论是函数还是任何其他类型)中返回“字段”的值。

    索引运算符按通用顺序排列:

    1. 表达式[ 表达式2 ]
    2. 表达式.标识符
    3. 表达式:标识符(参数列表)

    后两者只是“语法糖”,可以改写成上面的任何形式。

    如果 "expression2" 始终是作为有效 Lua 标识符的相同字符串并且您想对其进行硬编码,则您将使用第二个。

    如果通过索引“标识符”与“表达式”返回的值将始终是您希望使用“表达式”返回的值作为隐式第一个参数来调用的函数,则您将使用第三个。这样的函数调用称为“方法调用”。

    另外,请注意,语言/编译器并不关心/知道字段值是否是函数(如果您尝试调用不是函数的值,则会在运行时出错)。它也不关心/知道该函数是否是一个方法(如果您不传递适当的参数,该函数可能不会像您预期的那样运行)。

    现在,表达式值的类型必须是任何可以索引的类型。请注意,表达式没有编译时类型,因此如果表达式值是无法索引的类型,那也是运行时错误。可索引类型是:表和任何具有__index 元方法的对象。其他答案提供了有关这些的详细信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-16
      • 2020-01-06
      • 2011-02-24
      • 1970-01-01
      • 2020-08-14
      • 2022-11-10
      相关资源
      最近更新 更多