开发者

Knowing if a value can be called

开发者 https://www.devze.com 2023-04-07 04:50 出处:网络
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.

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:

  • a number that is an ammount of second
  • a callable value

By 'a callable value', I mean a value that can be called. This can be:

  • a function
  • a table with a metatable that allows calling (through a __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 d开发者_Python百科o 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.


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>)


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.


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?).

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号