【问题标题】:Is it possible to replicate Ruby's method_missing in Lua?是否可以在 Lua 中复制 Ruby 的 method_missing?
【发布时间】:2013-11-15 13:44:44
【问题描述】:

我相当确定在 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 的表的上下文中使用的每个变量都不会为零,因为我必须返回一个可以调用的对象,以便将索引函数的潜在调用pass the buck实际通话。

即如上所述定义全局method_missing后,尝试调用未定义的方法'test_print'按预期运行,但索引时test_print的值非nil,其他没有响应的方法/变量,如this_should_be_nil是非-无。

那么有可能避免这个陷阱吗?或者在不修改语言源本身的情况下可以不弯曲语法来支持这种修改吗?我想困难在于 Ruby 中的索引和调用是相似的,而在 Lua 中它们是不同的。

【问题讨论】:

  • 是否还有一个用例或一类用例您通常使用这种技术来解决?您可能应该在问题中提及它以避免XY problem。也许 lua 可以为它提供不同的方法。
  • 因为__index 如果值存在于表中则不会被调用,所以__index 函数的第一部分没有用。

标签: syntax lua metaprogramming method-missing


【解决方案1】:

您已经很好地确定了问题:据我所知,在纯 Lua 中解决该问题是不可能的。

编辑:我错了,您可以通过使 nil 可调用。查看其他答案。 IMO 仍然是个坏主意。 method_missing 的主要用例是代理对象,您可以通过其他方式解决这个问题。 method_missing on Kernel (Ruby) / _G (Lua) 很糟糕:)

可以做的只是处理一些方法,例如,如果你知道你希望方法以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,并尽可能选择动态方法生成。

【讨论】:

    【解决方案2】:

    因此,根据@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
    

    这个 sn-p 让您可以使用 method_missing,这与在 Ruby 中的使用方式非常相似(尽管没有任何响应检查)。这与我最初的反应相似,只是它通过 nil 的元表“推卸责任”,这是我认为我做不到的。 (感谢您的提示!)但正如@greatwolf 所说,可能没有理由在 Lua 中使用这样的构造;通过更清晰的元方法操作可能可以实现相同的动态。

    【讨论】:

      【解决方案3】:

      您可以通过使nil 值可调用来避免此问题。
      不幸的是,这只能通过宿主代码(即 C 程序)来完成,而不能通过 Lua 脚本来完成。

      帕斯卡码:

      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
      

      【讨论】:

      • 啊哈!我相信这足以让它按预期运行,尽管可能会有一些意想不到的后果......
      • @WesleyWigham 这似乎是语法糖。为什么不直接使用rawget 进行检查,从而完全避免这种棘手的黑客攻击?您可以将其放在表或元表中以使其更易于使用。例如。它的语法用法变为_G:exists "this_should_be_nil" 而不是_G.this_should_be_nil
      • 对我来说挑战不是解决需要动态方法的问题(毫无疑问,Lua 中有比复制 method_missing 更好的方法),而只是看看在语言上是否可以复制它。有了这最后一块,它似乎就是这样。 (尽管需要注意的是,除非您还实现了类似“responds_to”的函数,否则它会使所有对 nil 的调用保持沉默)
      • @lhf 这将使 all nil 值可调用,这是 Lua 语言的一个非常基本的变化。例如,(nil)() 突然成为完全有效的代码。这也不是 Ruby 中的 method_missing 方法所做的。
      猜你喜欢
      • 2011-12-05
      • 2016-03-11
      • 2011-10-20
      • 2013-09-24
      • 2012-04-04
      • 2011-02-21
      相关资源
      最近更新 更多