能够在Lua中复制Ruby的method_missing方法吗?

我相当确定,在 Lua 中,您可以使用给定 metatable 的 __index__newindex__call 来大致复制 Ruby 的 method_missing。我有一些代码:

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)

我的问题是:尽管语法相似,我肯定可以使用它来复制功能,但它会引入一个错误。您在应用 method_missing 的表的上下文中使用的每个变量都不为nil,因为我必须返回一个可调用的对象,以便将可能的调用从索引函数传递给实际调用。

例如:在上述定义了全局 method_missing 后,尝试调用未定义的方法 'test_print' 会按预期运行,但是索引时 test_print 的值是非 nil 的,并且未响应的其他方法/变量,例如 this_should_be_nil也是非 nil 的。

那么有可能避免这个陷阱吗?或者语法不能被扭曲以支持此修改,而不修改语言源代码本身?我想困难在于,在 Ruby 中,索引和调用是类似的,而在 Lua 中它们是不同的。

点赞
用户204011
用户204011

你已经很好地找出了问题:就我所知,无法通过纯 Lua 解决这个问题。

编辑:我错了,你可以通过将 nil 作为调用对象来解决这个问题。请参阅其他回答。但在我看来,这仍然是一个糟糕的想法。method_missing 的主要用途是代理对象,你可以用其他方式解决这个问题。在 Kernel(Ruby) / _G(Lua) 上使用method_missing 是糟糕的 :)

可以 做的是只处理 一些 方法,例如如果你知道希望以 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)

现在也许问题应该是:你为什么想复制 method_missing,你能避免它吗?即使在 Ruby 中,建议避免使用 method_missing 并在可能时选择动态方法生成。

2013-11-04 22:28:48
用户1847592
用户1847592

你可以通过将 nil 值作为可调用函数来避免这个问题。

不幸的是,这只能从主机代码(即 C 程序)中完成,而不能从 Lua 脚本中完成。

Pascal 代码:

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;

输出:

true
This method is under construction
2013-11-04 22:57:05
用户2953888
用户2953888

所以根据 @lhf 的提示,我已经成功实现了 method_missing 的一个可以接受的解决方案(就我所知)。最终,我开发出了以下代码:

local field = '__method__missing'

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

        rawget(self, name)[field] = function(...)
            return func(self, name, ...)
        end
    end

    setmetatable(selfs, meta)
end

debug.setmetatable(nil, { __call = function(self, ...)
    if self[field] then
        return self[field](...)
    end
    return nil
end, __index = function(self, name)
    if name~=field then error("attempt to index a nil value") end
    return getmetatable(self)[field]
end, __newindex = function(self, name, value)
    if name~=field then error("attempt to index a nil value") end
    getmetatable(self)[field] = value
end} )

_G:method_missing(function(self, name, ...)
    local args = {...}
    if name=="test_print" then
        print("Oh my lord, it's method missing!", ...)
        return
    elseif args[1] and string.find(name, args[1]) then --If the first argument is in the name called...
        table.remove(args, 1)
        return unpack(args)
    end
end)

test_print("I like me some method_missing abuse!")
test_print("Do it again!")

print(test_print, "method_missing magic!")
print(this_should_be_nil == nil, this_should_be_nil() == nil)

print(conditional_runs("runs", "conditionally", "due to args"))
print(conditional_runs("While this does nothing!")) --Apparently this doesn't print 'nil'... why?

输出:

Oh my lord, it's method missing!        I like me some method_missing abuse!
Oh my lord, it's method missing!        Do it again!
nil     method_missing magic!
true    true
conditionally   due to args

这段代码让你可以很类似地在 Lua 中使用 method_missing,就像在 Ruby 中一样(没有任何响应检查)。它类似于我的初始响应,但它通过 nil 的元表“传递了 Buck”,这是我以为我无法做的事情。 (感谢提示!)但正如 @greatwolf 所说,在 Lua 中可能没有理由使用这样的结构;相同的动态性可能可以通过更清晰的元方法操纵来实现。

2013-11-05 04:24:02