2013-11-04 6 views
6

Ich bin mir ziemlich sicher, dass Sie in Lua die __index, __newindex und __call einer gegebenen Metatabelle verwenden können, um Rubys method_missing grob zu replizieren. Und ich habe etwas:Ist es möglich, Rubys method_missing in Lua zu replizieren?

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) 

Mein Problem ist folgendes: Während die Syntax ähnlich ist, und ich kann es auf jeden Fall die Funktionalität zu replizieren verwenden, es führt einen Bruchfehler. Jede einzelne Variable, die Sie im Kontext der Tabelle verwenden, in der Sie eine method_missing anwenden, ist nie null, da ich ein Objekt, das aufgerufen werden kann, um pass the buck des potenziellen Aufrufs von der Indexfunktion zu einem tatsächlichen Aufruf zurückgeben kann.

ie Nach dem Definieren eines globalen method_missing wie oben wird der Versuch, die undefinierte Methode 'test_print' aufzurufen, wie erwartet ausgeführt, aber der Wert von test_print bei der Indizierung ist nicht nil und andere Methoden/Variablen, auf die nicht geantwortet wird this_should_be_nil sind nicht-Null.

So ist es möglich, diese Falle zu vermeiden? Oder kann die Syntax nicht so geformt werden, dass sie diese Modifikation unterstützt, ohne die Sprachquelle selbst zu verändern? Ich stelle mir vor, die Schwierigkeit besteht darin, dass in Ruby Indexierung und Aufruf analog sind, während sie in Lua verschieden sind.

+0

Gibt es auch einen Anwendungsfall oder eine Klasse von Anwendungsfällen, die Sie normalerweise mit dieser Technik lösen? Sie sollten es wahrscheinlich in Ihrer Frage erwähnen, um das [XY Problem] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) zu vermeiden. Vielleicht kann Lua einen anderen Ansatz dafür bieten. – greatwolf

+0

Da '__index' nicht aufgerufen wird, wenn der Wert in einer Tabelle existiert, hat der erste Teil der' __index' -Funktion keine Verwendung. – dualed

Antwort

2

Sie haben das Problem gut erkannt: Es ist meines Wissens nicht möglich, dieses Problem in reinem Lua zu lösen.

EDIT: Ich lag falsch, Sie können nil aufrufbar machen. Siehe andere Antworten. Es ist immer noch eine schlechte Idee, IMO. Der Hauptanwendungsfall für method_missing sind Proxy-Objekte, und Sie können das auf andere Weise lösen. method_missing auf Kernel (Rubin)/_G (Lua) ist schrecklich :)

Was Sie tun könnte, ist einige Methoden nur handhaben, zum Beispiel, wenn Sie wissen, dass Sie Methoden erwarten, die von test_ starten:

Jetzt sollte vielleicht die Frage sein: Warum willst du method_missing replizieren, und können Sie es vermeiden? Auch in Ruby wird empfohlen, die Verwendung von method_missing zu vermeiden und wenn möglich die dynamische Methodengenerierung zu bevorzugen.

3

Sie können dieses Problem vermeiden, indem Sie nil Wert aufrufbar machen.
Leider kann dies nur von Host-Code (d. H. C-Programm), nicht von Lua-Skript durchgeführt werden.

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; 

Ausgang:

true 
This method is under construction 
+5

Sie können 'debug.setmetatable (null, {__ call = f})', – lhf

+0

Aha! Ich glaube, das wird ausreichen, um es wie erwartet funktionieren zu lassen, auch wenn es unbeabsichtigte Folgen haben könnte ... –

+0

@WesleyWigham Das scheint Syntax Zucker zu sein. Warum nicht einfach "rawget" verwenden, um die Prüfung durchzuführen, die den komplizierten Hack nicht benötigt? Sie können es in die Tabelle oder Metatabelle einfügen, um es einfacher zu verwenden. z.B. Die Syntax-Verwendung wird '_G: exists 'this_should_be_nil" 'anstelle von' _G.this_should_be_nil' – greatwolf

1

So mit der Spitze von @lhf, ich habe eine passable Doppel geschaffen (von dem, was ich sagen kann) von method_missing.Am Ende entwickelte ich folgendes:

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? 

Ausgang:

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 

Dieser Code-Schnipsel können Sie method_missing ziemlich ähnlich wie verwenden können Sie in Ruby (mit keine Antwort überhaupt überprüft, obwohl). Es ist vergleichbar mit meiner ursprünglichen Antwort, außer dass es "den Bock" durch Nils Metatable weitergibt, etwas, von dem ich dachte, dass ich es nicht tun könnte. (Danke für den Tipp!) Aber wie @greatwolf sagt, gibt es wahrscheinlich keinen Grund, jemals ein Konstrukt wie dieses in Lua zu benutzen; Die gleiche Dynamik kann wahrscheinlich durch klarere Metamethodenmanipulationen erreicht werden.

Verwandte Themen