How does the __call metamode work in Lua 5.1?

I'm trying, as an exercise, to make a set implementation in Lua. In particular, I want to use the simplified implementation of Pil2 11.5 and grow it to enable the ability to insert values, delete values, etc.

Now the obvious way to do this (and the way it works):

Set = {} function Set.new(l) local s = {} for _, v in ipairs(l) do s[v] = true end return s end function Set.insert(s, v) s[v] = true end ts = Set.new {1,2,3,4,5} Set.insert(ts, 5) Set.insert(ts, 6) for k in pairs(ts) do print(k) end 

As expected, I get printed numbers from 1 to 6. But these calls to Set.insert(s, value) really pretty ugly. I would rather call something like ts:insert(value) .

My first attempt at such a solution looked like this:

 Set = {} function Set.new(l) local s = { insert = function(t, v) t[v] = true end } for _, v in ipairs(l) do s[v] = true end return s end ts = Set.new {1,2,3,4,5} ts:insert(5) ts:insert(6) for k in pairs(ts) do print(k) end 

This works basically fine until you see what comes of it:

 1 2 3 4 5 6 insert 

It is very obvious that the insert function, which is a member of the table, is displayed. Not only is it uglier than the original problem of Set.insert(s, v) , it is also prone to serious problems (for example, what happens if "insert" is a valid key that someone is trying to enter?). It's time to hit the books again. What happens if I try this instead ?:

 Set = {} function Set.new(l) local s = {} setmetatable(s, {__call = Set.call}) for _, v in ipairs(l) do s[v] = true end return s end function Set.call(f) return Set[f] end function Set.insert(t, v) t[v] = true end ts = Set.new {1,2,3,4,5} ts:insert(5) ts:insert(6) for k in pairs(ts) do print(k) end 

Now I am reading this code:

  • When I call ts:insert(5) , the fact that insert does not exist for the call means that the metatet ts will look for "__call" .
  • Metatable "__call" ts returns Set.call .
  • Now Set.call is called with the name insert , which forces it to return the Set.insert function.
  • Set.insert(ts, 5) .

What really happens:

 lua: xasm.lua:26: attempt to call method 'insert' (a nil value) stack traceback: xasm.lua:26: in main chunk [C]: ? 

And at that moment I'm at a standstill. I have no idea where to go from here. I cracked for an hour with varying degrees of increasingly desperate variations of this code, but the end result is that I can’t do anything. What is undoubtedly obvious in this matter?

+6
source share
4 answers

Now I am reading this code:

  • When I call ts: insert (5), the fact that the insert does not exist for the call means that ts metatable will look for "__call".

Your problem is there. The __call __call should be handled when the table itself is called (i.e., as a function):

 local ts = {} local mt = {} function mt.__call(...) print("Table called!", ...) end setmetatable(ts, mt) ts() --> prints "Table called!" ts(5) --> prints "Table called!" and 5 ts"String construct-call" --> prints "Table called!" and "String construct-call" 

Object-oriented colon calls in Lua, such as:

 ts:insert(5) 

are just syntactic sugar for

 ts.insert(ts,5) 

which in itself is syntactic sugar for

 ts["insert"](ts,5) 

Thus, the action that is performed in ts is not a call , but index (the result of ts["insert"] is what is called), which is determined by the __index __index .

The __index __index can be a table for the simple case when you want the index to β€œreturn” to another table (note that this is the value of the __index key in the metathe, which is indexed, not metatetized):

 local fallback = {example = 5} local mt = {__index = fallback} local ts = setmetatable({}, mt) print(ts.example) --> prints 5 

The __index __index as a function works similarly to the signature you expected with Set.call, except that it passes the indexed table before the key:

 local ff = {} local mt = {} function ff.example(...) print("Example called!",...) end function mt.__index(s,k) print("Indexing table named:", s.name) return ff[k] end local ts = {name = "Bob"} setmetatable(ts, mt) ts.example(5) --> prints "Indexing table named:" and "Bob", --> then on the next line "Example called!" and 5 

For more information on metatables, refer to the manual .

+16
source

You said:

Now I am reading this code:

  • When I call ts: insert (5), the fact that the insert does not exist is called tat metatable to search for "__call".
  • The key __call ts metatable returns Set.call.
  • Now Set.call is called with the name that it calls, it returns the Set.insert function.
  • Set.insert (ts, 5) is called.

No, what happens:

  • When insert not found directly in the ts object, Lua looks for __index in its meta.
    • If it is, and this is a table, Lua will look for insert there.
    • If it exists and it is a function, it will call it with the original table ( ts in this case) and the search key ( insert ).
    • If not, then it is considered nil .

The error you encountered is due to the fact that you do not have __index installed in your meta, so you are actually calling nil .

This can be solved by pointing __index to some table, namely Set , if you are going to store your methods there.

As for __call , it is used when you call an object as a function. I.e:

 Set = {} function Set.new(l) local s = {} setmetatable(s, {__index=Set, __call=Set.call}) for _, v in ipairs(l) do s[v] = true end return s end function Set.call(s, f) -- Calls a function for every element in the set for k in pairs(s) do f(k) end end function Set.insert(t, v) t[v] = true end ts = Set.new {1,2,3,4,5} ts:insert(5) ts:insert(6) ts(print) -- Calls getmetatable(ts).__call(ts, print), -- which means Set.call(ts, print) -- The way __call and __index are set, -- this is equivalent to the line above ts:call(print) 
+10
source
 Set = {} function Set.new(l) local s = {} setmetatable(s, {__index=Set}) for _, v in ipairs(l) do s[v] = true end return s end function Set.call(f) return Set[f] end function Set.insert(t, v) t[v] = true end ts = Set.new {1,2,3,4,5} ts:insert(5) ts:insert(6) for k in pairs(ts) do print(k) end 
+4
source

I changed your first version and this version will offer the features that I think you are looking for.

 Set = {} Set.__index = Set function Set:new(collection) local o = {} for _, v in ipairs(collection) do o[v] = true end setmetatable(o, self) return o end function Set:insert(v) self[v] = true end set = Set:new({1,2,3,4,5}) print(set[1]) --> true print(set[10]) --> nil set:insert(10) print(set[10]) --> true 
+3
source

Source: https://habr.com/ru/post/888447/


All Articles