As a total Lua newbie, it took me a while to understand @Yu Hao's answer, so I'll try to add some details for other beginners. Please correct me if anything is wrong.
As far as I can see, a call like x:someFunc()
works if [*]:
x
has a metatable
- and the metatable has a field
__index
- which points to a table containing a function
someFunc
.
As Yu Hao has pointed out, strings automatically get a metatable pointing to the table string
, e.g.:
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
}
}
So in this case s:myFunc()
works automatically. In order to use the colon syntax for a table
, we can manually set its metatable:
th> function enableColonForTable(t)
..> meta = {__index = table}
..> setmetatable(t, meta)
..> end
th> t = {}
th> enableColonForTable(t)
th> t:insert(1) -- works now!
Another observation is that it actually does not matter whether __index
points to a table with exactly the same name as the type. Instead of meta = {__index = table}
, we also could do:
th> arbitraryScope = {}
th> function arbitraryScope:test() return "something" end
th> t = {}
th> setmetatable(t, {__index = arbitraryScope})
{}
th> t:test()
something
That is also the key difference to the case of a number
. While there are existing tables called string
and table
, there is no existing table called number
. This is why even defining e.g. function number:abs()
has failed before. But we can still make it work:
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
Note that we had to use debug.setmetatable
instead of setmetatable
here. The difference between the two seems to be that setmetatable
sets the metatable only for the given instance, while debug.setmetatable
sets the metatable for the whole type. Apparently setting an individual metatable for numbers is forbidden (and would not make much sense anyways). This means that (in contrast to tables) newly constructed numbers now have the given metatable by default, so this works:
th> y = -42
th> y:abs()
42
[*] Update
As pointed out by Tom Blodget, x:someFunc()
also works if x
itself serves as a namespace, i.e., it is a table with a method field someFunc
. For instance you could do table:insert(1)
. But now the namespace (the table called table
) is passed as self
and you would have added data to the namespace:
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
}