Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to replicate Ruby's method_missing in Lua?

I'm fairly sure that in Lua, you can use a given metatable's __index, __newindex, and __call to roughly replicate Ruby's method_missing. And I somewhat have:

function method_missing(selfs, func)

    local meta = getmetatable(selfs)
    local f
    if meta then
        f = meta.__index
    else
        meta = {}
        f = rawget
    end
    meta.__index = function(self, name)
        local v = f(self, name)
        if v then
            return v
        end

        local metahack = {
            __call = function(self, ...)
                return func(selfs, name, ...)
            end
        }
        return setmetatable({}, metahack)
    end

    setmetatable(selfs, meta)
end

_G:method_missing(function(self, name, ...)
    if name=="test_print" then
        print("Oh my lord, it's method missing!", ...)
    end
end)

test_print("I like me some method_missing abuse!")

print(this_should_be_nil)

My problem is this: While the syntax is similar, and I can certainly use it to replicate the functionality, it introduces a breaking error. Every single variable that you use in the context of the table you apply a method_missing to is never nil, since I have to return an object that can be called in order to pass the buck of the potential call from the index function to an actual call.

i.e. After defining a global method_missing as above, attempting to call undefined method 'test_print' runs as expected, but the value of test_print when indexed is non-nil, and other methods/variables that aren't responded to, like this_should_be_nil are non-nil.

So is it possible to avoid this pitfall? Or can the syntax not be bent to support this modification without modifying the language source itself? I imagine the difficulty arises in how in Ruby, indexing and calling are analogous, whereas in Lua they are distinct.

like image 855
Wesley Wigham Avatar asked Nov 04 '13 20:11

Wesley Wigham


2 Answers

You can avoid this problem by making nil value callable.
Unfortunatelly, this can be done only from host code (i.e., C program), not from Lua script.

Pascal code:

function set_metatable_for_any_value_function(L: Plua_State): Integer; cdecl;
begin   // set_metatable_for_any_value(any_value, mt)
   lua_setmetatable(L, -2);
   Result := 0;
end;

procedure Test_Proc;
   var
      L: Plua_State;
   const
      Script =
'set_metatable_for_any_value(nil,                                        ' +
' {                                                                      ' +
'   __call = function()                                                  ' +
'              print "This method is under construction"                 ' +
'            end                                                         ' +
' }                                                                      ' +
')                                                                       ' +
'print(nonexisting_method == nil)                                        ' +
'nonexisting_method()                                                    ';
begin
   L := luaL_newstate;
   luaL_openlibs(L);
   lua_pushcfunction(L, lua_CFunction(@set_metatable_for_any_value_function));
   lua_setglobal(L, 'set_metatable_for_any_value');
   luaL_dostring(L, Script);
   lua_close(L);
end;

Output:

true
This method is under construction
like image 50
Egor Skriptunoff Avatar answered Oct 24 '22 01:10

Egor Skriptunoff


You have identified the problem well: it is not, as far as I know, possible to solve that issue in pure Lua.

EDIT: I was wrong, you can by making nil callable. See other answers. It is still a bad idea IMO. The main use case for method_missing is proxy objects and you can solve that in another way. method_missing on Kernel (Ruby) / _G (Lua) is terrible :)

What you could do is only handle some methods, for instance if you know you expect methods that start by test_:

local function is_handled(method_name)
    return method_name:sub(1,5) == "test_"
end

function method_missing(selfs, func)

    local meta = getmetatable(selfs)
    local f
    if meta then
        f = meta.__index
    else
        meta = {}
        f = rawget
    end
    meta.__index = function(self, name)
        local v = f(self, name)
        if v then
            return v
        end

        if is_handled(name) then
            local metahack = {
                __call = function(self, ...)
                    return func(selfs, name, ...)
                end
            }
            return setmetatable({}, metahack)
        end
    end

    setmetatable(selfs, meta)
end

_G:method_missing(function(self, name, ...)
    if name=="test_print" then
        print("Oh my lord, it's method missing!", ...)
    end
end)

test_print("I like me some method_missing abuse!")

print(this_should_be_nil)

Now maybe the question should be: why do you want to replicate method_missing, and can you avoid it? Even in Ruby it is advised to avoid the use of method_missing and prefer dynamic method generation when possible.

like image 40
catwell Avatar answered Oct 24 '22 01:10

catwell