I have a pretty mind-bending setup right now. I have a regular function that returns a table with functions in it under keys "string" and "number":
function defGeneric()
local function funcNumber(a)
return 2*a^2
end
local function funcString(a)
return a.." - test"
end
local returnTable={}
returnTable["number"]=funcNumber
returnTable["string"]=funcString
return returnTable
end
And that works fine. But what I want to do now is make the table that this function returns callable. To illustrate, let's say we have v=defGeneric(). Specifically:
If v is called with a string str, return the result of v["string"](str)
If v is called with a number n, return the result of v["number"](n)
This is obviously a job for metatables, so I can (in my function) add the code to set a metatable:
local metaTable = {
__call = function (...) -- "call" event handler
return
end
}
setmetatable(returnTable,metaTable)
But I don't know what I would put after that return statement. I don't think I can reference returnTable, because this table will be called like so:
v=defGeneric()
v("test")
And I need to reference v's "string" function (there certainly could be multiple defGeneric() tables in one program).
I think the answer here might be some self trick but I can't wrap my head around how. How do I reference a metatable's table from the metatable?
The first argument passed to the __call function is the table it is being called on, the table returned from the function in this case. You can use type(a) to get the type of the argument as a string, so you could do something like this:
function defGeneric()
local result = {
['number'] = function(a) return 2*a^2 end,
['string'] = function(a) return a.." - test" end
}
setmetatable(result, {
__call = function(t,a)
local f = t[type(a)]
if f == nil then return "No handler for type "..type(a) end
-- alternate:
-- if f == nil and t['string'] ~= nil then return t['string'](tostring(a)) end
return f(a)
end
})
return result
end
local def = defGeneric()
print("string: "..tostring(def('sample string')))
print("number: "..tostring(def(5)))
print("table: "..tostring(def({})))
print("boolean: "..tostring(def(1 > 5)))
output
string: sample string - test
number: 50.0
table: No handler for type table
boolean: No handler for type boolean
alternate output
string: sample string - test
number: 50.0
table: table: 0x18537e0 - test
boolean: false - test
Related
I have created a small class called Piece, which looks as follows
local Piece = {}
local Piece_mt = { __index = Piece}
function Piece.New(name, img, startPosX, startPosY)
newPiece = {}
newPiece.name = name;
newPiece.img = display.newImage(img, startPosX, startPosY);
print(newPiece.name);
local function OnHit ( event )
if event.phase == "ended" then
print(newPiece.name);
end
end
newPiece.img:addEventListener("touch", OnHit);
return setmetatable( newPiece, Piece_mt )
end
return Piece
When I create the class, it prints the name, and it is correct (matches what I passed as a parameter). However, when I later click the image, and the name is printed as a result of the triggered OnHit function, the name printed is always the name of the last object I created, regardless of which of the objects I click on. Could someone explane why this happens and how to fix it?
function Piece.New(name, img, startPosX, startPosY)
newPiece = {}
-- process
return setmetatable( newPiece, Piece_mt )
end
Here, you are assigning the global variable newPiece and return it every time. Instead, try use a local variable:
function Piece.New(name, img, startPosX, startPosY)
local newPiece = {}
-- process
return setmetatable( newPiece, Piece_mt )
end
I just started programming in lua and I created sort of oop structure following this tutorial: http://tylerneylon.com/a/learn-lua/
Problem is, when I created function that returns object or table of objects and call it inside constructor, it returns nil.
Here is my code for first object:
require "ObjectB"
ObjectA = {}
function ObjectA:new(num)
newInstance = {}
newInstance.var = self:foo(num)
self.__index = self
return setmetatable(newInstance, self)
end
function ObjectA:foo(num)
return ObjectB:new(num)
end
, and for second object:
ObjectB = {}
function ObjectB:new(num)
newInstance = {}
newInstance.num = num
self.__index = self
return setmetatable(newInstance, self)
end
When I do this:
myObject = ObjectA:new(5)
print(myObject.var.num)
, I get error: "Error: main.lua:14: attempt to index field 'var' (a nil value)".
But when I do this:
myObject = ObjectA:new(5)
myObject.var = ObjectA:foo(5) //setting var by calling foo outside of constructor
print(myObject.var.num)
, everything seems to work fine and print result is really 5. Can anyone tell me what is reason for this strange behaviour or what am I doing wrong here?
Variables are global by default, so the two variables newInstance in ObjectA:new and ObjectB:new are the same global variables, you assign it a new value, the previous value is gone.
Instead, use local variables like this:
function ObjectA:new(num)
local newInstance = {}
--the rest
end
and
function ObjectB:new(num)
local newInstance = {}
--the rest
end
I have the following Class
local PROGRESS = {}
PROGRESS.__index = function(self,key)
if key~="__group" and self.__group[key] then
return self.__group[key]
else
return rawget(self,key)
end
end
What this does is when You access table[key] it performs a lookup in table.__group (which is an object of another class) and returns table.__group[key] ,if it is not nil.
Now I am trying to do the same for member functions.
i.e If I call table:key() a lookup must be performed in table.__group and if the function is present, then table.__group:key() should be called.
How do I accomplish this?
I tried to do this.
local PROGRESS = {}
PROGRESS.__index = function(self,key)
if key~="__group" and self.__group[key] then
local val = self.__group[key]
if type(val) == "function" then
self.__group:val()
return function() end
end
return self.__group[key]
else
return rawget(self,key)
end
end
But there are 2 things wrong here.
I am unable to retrieve the original function's arguments
Event if I just ACCESS table[key].function without calling it, the function will be called
And I've got the feeling that I am trying to complicate things and the solution is way simpler.
Any help is appreciated.
UPDATE
#Mud
The problem with the original code is that the object passed as 'self' to the member function is an object of the new class. Not of the old class.
Consider this code
GROUP_CLASS = {}
GROUP_CLASS.__index = GROUP_CLASS
function GROUP_CLASS:showSum (a,b) print(self);print(a + b) end
group_object = setmetatable({},GROUP_CLASS)
group_object:showSum(1,2)
local PROGRESS_CLASS = {}
PROGRESS_CLASS.__index = function(self,key,value)
if key~="__group" and self.__group[key] then
return self.__group[key]
else
return rawget(self,key)
end
end
progress_object = setmetatable( {__group = group_object} , PROGRESS_CLASS)
progress_object:showSum(3,3)
--progress_object is passed as first argument to showSum. But i need group_object to be passed
In the above code, When progress_object:showSum(3,3) is called,
is it possible to pass group_object (or in other words progress_object.__group) as self instead of progress_object.
Hope that makes sense.
Response to updated post:
progress_object is passed as first argument to showSum. But i need group_object to be passed
If you're going to ignore the state of the object a method is called on, and substitute the state of some other object, why is it even a method on that object? That's like overriding the addition operator to do multiplication, a recipe for confusion.
In other words, you want this:
progress_object:method("foo")
To resolve, via bizarre internal machinery, into this:
group_object:method("foo")
Why not skip a step and just make the latter call?
If you must, you could achieve this by returning a proxy for the method which replaces self with __group
local PROGRESS_CLASS = {}
PROGRESS_CLASS.__index = function(self,key)
local groupval = self.__group[key]
if key == '__group' or not groupval then
return rawget(self,key)
elseif type(groupval) ~= 'function' then
return groupval
else
return function(...)
if self == ... then -- method call
-- replace self argument with __group
return groupval(self.__group,select(2,...))
else
return groupval(...)
end
end
end
end
Response to original post:
How I am trying to do the same for member functions. i.e If I call table:key() a lookup must be performed in table.__group and if the function is present, then table.__group:key() should be called.
How do I accomplish this?
Do nothing. Your original code handles this.
Lua doesn't know what a "member function" is. A member is a member (i.e. an element in a table), and whether the value of that member is a function is irrelevant.
Remember:
obj:method(a,b,c) is exactly equivalent to obj.method(obj,a,b,c)
obj.method is exactly equivalent to obj["method"].
Your code already resolves obj["method"] into obj.__group["method"]
So you're done.
For instance, say we have:
group = {}
function group:showSum (a,b) print(a + b) end
function group:showProduct(a,b) print(a * b) end
Using your first code, we can write:
foo = setmetatable({__group = group}, PROGRESS)
foo:showSum(3,3) -- 6
foo:showProduct(3,3) -- 9
That's it.
Now, as long as we're here, let's look at what your second function is doing:
local val = self.__group[key]
if type(val) == "function" then
self.__group:val()
return function() end
end
First you grab the function value from __group. At this point you're done. Simply return that value, and the caller is going to call that value (i.e. (...)). Instead, you call __group["val"] which is likely a totally different function from __group[key] (unless key=="val"), then you pass the caller a function which does nothing.
So, I'm trying to write a simple class in Lua for representing CSV fields:
csv_entry = {}
csv_entry.__index = csv_entry
function csv_entry.create(...)
return arg
end
function csv_entry:tostring()
local str = string.char()
for i,v in ipairs(self) do
if i < #self then
str = str .. v
else
str = str .. v .. ", "
end
end
end
function csv_entry:print()
print(self:tostring())
end
But when I try to use this class like this:
c = csv_entry.create("Volvo", 10000, "Eric")
c:print() -- line 25
I get the error message
lua: csv.lua:25: attempt to call method 'print' (a nil value)
And I can't really figure out the issue here. What am I doing wrong?
You probably meant to do is this:
function csv_entry.create(...)
return setmetatable(arg, csv_entry)
end
Your posted version of cvs_entry.create just returns it's arguments packed into a table, so this code:
c = csv_entry.create("Volvo", 10000, "Eric")
c:print()
Is exactly equivalent to this code:
c = {"Volvo", 10000, "Eric"}
c:print()
c doesn't contain a print entry, so c.print returns nil and c:print() fails because you're trying to "call" nil.
Side note: the implicit arg parameter to variadic functions was removed in Lua 5.1 (6 years ago). The correct way to do this now is:
function csv_entry.create(...)
local arg = {...}
return setmetatable(arg, csv_entry)
end
Or simply:
function csv_entry.create(...)
return setmetatable({...}, csv_entry)
end
As long as we're here: you're going to get no output from csv_entry:tostring because it doesn't return anything. Also, if all you're trying to do is to concatenate a bunch of items with comma separators, you can use table.concat:
function csv_entry:tostring()
return table.concat(self, ', ')
end
I rewrite your code to meet what it is for, it runs OK for me:
csv_entry = {}
function csv_entry:create(...)
o = {content = {}}
self.__index = self;
setmetatable(o, self)
for i = 1, arg.n do
o.content[i] = arg[i];
end
return o;
end
function csv_entry:tostring()
local resStr = ""
for i, v in pairs(self.content) do
resStr = resStr .. v;
if i < #(self.content) then
resStr = resStr .. ", "
end
end
return resStr;
end
function csv_entry:print()
print(self:tostring())
end
c = csv_entry:create("Volvo", 10000, "Eric")
c:print()
Like #Mud said, the create(...) in your code is just a regular call and returns all arguments from ..., if you want csv_entry works like a class, then you have to put codes which set metatable and __index into create(...), and return an instance from csv_entry class
Is it possible in lua to execute a function from a string representing its name?
i.e: I have the string x = "foo", is it possible to do x() ?
If yes what is the syntax ?
To call a function in the global namespace (as mentioned by #THC4k) is easily done, and does not require loadstring().
x='foo'
_G[x]() -- calls foo from the global namespace
You would need to use loadstring() (or walk each table) if the function in another table, such as if x='math.sqrt'.
If loadstring() is used you would want to not only append parenthesis with ellipse (...) to allow for parameters, but also add return to the front.
x='math.sqrt'
print(assert(loadstring('return '..x..'(...)'))(25)) --> 5
or walk the tables:
function findfunction(x)
assert(type(x) == "string")
local f=_G
for v in x:gmatch("[^%.]+") do
if type(f) ~= "table" then
return nil, "looking for '"..v.."' expected table, not "..type(f)
end
f=f[v]
end
if type(f) == "function" then
return f
else
return nil, "expected function, not "..type(f)
end
end
x='math.sqrt'
print(assert(findfunction(x))(121)) -->11
I frequently put a bunch of functions in a table:
functions = {
f1 = function(arg) print("function one: "..arg) end,
f2 = function(arg) print("function two: "..arg..arg) end,
...,
fn = function(arg) print("function N: argh") end,
}
Then you can use a string as an table index and run your function like this
print(functions["f1"]("blabla"))
print(functions["f2"]("blabla"))
This is the result:
function one: blabla
function two: blablablabla
I find this to be cleaner than using loadstring(). If you don't want to create a special function table you can use _G['foo'].
loadstring is not the answer here. For starters you would need a return in the string, and other details I won't go into.
THC4k has the right idea; if you have the function name in the variable x, then the call you want is
_G[x](arg1, arg2, ...)
Names are not unique, there can be many functions names foo in different namespaces. But _G['foo'] is foo in the global namespace.
It sounds like you want to do an 'eval', which is supported in Lua like so:
assert(loadstring(x))()
You'll probably want to concatenate the "()" onto x first, though.