Lua中是否有可能复制Ruby的method_missing方法?

6

我相信在Lua中,您可以使用给定的元表的__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中它们是不同的。

你通常使用这种技术解决什么样的用例或用例类别?为了避免 XY 问题,你应该在问题中提到它。也许 Lua 可以为此提供不同的方法。 - greatwolf
由于如果值存在于表中,则不会调用__index,因此您的__index函数的第一部分没有用处。 - dualed
3个回答

3
您可以通过使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

啊哈!我相信这将足以让它按预期运行,尽管可能会有一些意外后果... - Wesley Wigham
这似乎是语法糖。为什么不直接使用“rawget”进行检查,这样可以避免整个棘手的hack? 您可以将其放入表格或元表中,以使其更易于使用。例如,它的语法用法变成了“_G:exists "this_should_be_nil"”,而不是“_G.this_should_be_nil”。 - greatwolf
对我来说,挑战并不在于解决需要动态方法的问题(毫无疑问,在Lua中有比复制method_missing更好的方法),而仅仅是看看是否在语言上可以复制它。通过最后这个部分,似乎是可以的。(尽管有一个警告,除非您还实现了类似“responds_to”的函数,否则所有调用都会变成nil) - Wesley Wigham
@lhf 这将使得 所有 的 nil 值都可以被调用,这是 Lua 语言相当基础的变化。例如,(nil)() 突然成为了完全有效的代码。这也不是 Ruby 中 method_missing 方法所做的事情。 - dualed

2
您已经很好地找出了问题:就我所知,无法仅通过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,而是优先选择动态方法生成。

1

有了@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

这个片段让你可以使用method_missing,类似于Ruby中的用法(尽管没有响应检查)。它与我的初始答案类似,只是通过nil的元表“传递了问题”,这是我认为自己无法做到的事情。(感谢提示!)但正如@greatwolf所说,在Lua中可能永远不会使用这样的结构;通过更清晰的元方法操作,可能可以实现相同的动态性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接