Reputation: 63
Note that this question is about pure Lua. I do not have access to any module or the C side. Additionally, I can not use the IO, the OS or the debug library.
What I'm trying to make is a function that receives, as parameters:
By 'a callable value', I mean a value that can be called. This can be:
__call
metamethod)Here's an example of a callable table:
local t = {}
setmetatable(t, {
__call = function() print("Hi.") end
})
print(type(t)) --> table
t() --> Hi.
Here's the function:
function delay(seconds, func)
-- The second parameter is called 'func', but it can be anything that is callable.
coroutine.wrap(function()
wait(seconds) -- This function is defined elsewhere. It waits the ammount of time, in seconds, that it is told to.
func() -- Calls the function/table.
end)()
end
But I have a problem. I want the function to throw an error if the parameter 'func' is not callable.
I can check if it is a function. But what if it is a table with a metatable that allows calling?
If the metatable of the table is not protected by a __metatable
field, then, I can check the metatable to know if it is callable, but, otherwise, how would I do it?
Note that I have also thought about trying to call the 'func' parameter with pcall
, to check if it is callable, but to do that, I need to call it prematurely.
Basically, here's the problem: I need to know if a function/table is callable, but without trying to call it.
Upvotes: 6
Views: 2923
Reputation: 5921
function iscallable(x)
if type(x) == 'function' then
return true
elseif type(x) == 'table' then
-- It would be elegant and quick to say
-- `return iscallable(debug.getmetatable(x))`
-- but that is actually not quite correct
-- (at least in my experiments), since it appears
-- that the `__call` metamethod must be a *function* value
-- (and not some table that has been made callable)
local mt = debug.getmetatable(x)
return type(mt) == "table" and type(mt.__call) == "function"
else
return false
end
end
return iscallable
Then you can do
> = iscallable(function() end)
true
> = iscallable({})
false
> = iscallable()
false
> = iscallable(nil)
false
> x = {}
> setmetatable(x, {__call=function() end})
> = iscallable(x)
true
If you don't have access to the debug library, then you may have trouble being totally accurate since you can mess with metatables. You could use pcall
but its hard to separate out the errors properly. Even if you search for a particular error string as in the other answer here, that might be because of something inside the function being called not being callable, not this particular value not being callable, if that makes sense.
Upvotes: 3
Reputation: 15371
This attempt at refining Nicol's answer still has the problem that it needs to call the table, but it tells whether a supplied table actually was callable or not. Even if the table was callable, pcall()
will return false
if there is an error caused during the execution of the __call()
metamethod.
local result = {pcall(PossibleFunction, ...)}
if not result[1] then
if result[2]:match"^attempt to call" then
error("The provided value is not callable.")
else
-- Table was callable but failed. Just hand on the error.
error(result[2])
end
end
-- Do something with the results:
for i = 2, #result do
print(result[i])
end
Checking the error message in this way however does not feel very "clean" (what if the used Lua interpreter was e.g. modified with localized error messages?).
Upvotes: 1
Reputation: 473427
In general, if the metatable does not want you to be able to get it (by defining __metatable
to being something special), then you're not going to get it. Not from Lua.
However, if you want to cheat, you can always use debug.getmetatable
, which will return the metatable associated with that object.
You don't have to prematurely call anything with pcall
. Observe:
pcall(function(...) return PossibleFunction(...) end, <insert arguments here>)
Upvotes: 6