2013-11-04 11 views
6

Tôi khá chắc chắn rằng trong Lua, bạn có thể sử dụng một số có sẵn của metatable __index, __newindex__call để nhân rộng khoảng Ruby method_missing của Ruby. Và tôi phần nào có:Có thể nhân rộng method_missing của Ruby trong Lua không?

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) 

Vấn đề của tôi là: Trong khi cú pháp tương tự, và tôi chắc chắn có thể sử dụng nó để nhân rộng chức năng, nó giới thiệu lỗi vi phạm. Mỗi biến duy nhất mà bạn sử dụng trong ngữ cảnh của bảng mà bạn áp dụng method_missing sẽ không bao giờ là không, vì tôi phải trả về một đối tượng có thể được gọi để pass the buck của cuộc gọi tiềm năng từ hàm chỉ mục đến cuộc gọi thực tế.

ie Sau khi xác định phương thức global_missing như trên, cố gắng gọi phương thức undefined 'test_print' chạy như mong đợi, nhưng giá trị của test_print khi được lập chỉ mục là không nil và các phương thức/biến khác không được trả lời, như this_should_be_nil không phải là số không.

Vì vậy, có thể tránh được sự cố này không? Hoặc cú pháp có thể không được uốn cong để hỗ trợ sửa đổi này mà không sửa đổi nguồn ngôn ngữ chính nó? Tôi tưởng tượng những khó khăn phát sinh như thế nào trong Ruby, lập chỉ mục và gọi là tương tự, trong khi ở Lua họ là khác biệt.

+0

Có trường hợp sử dụng hoặc loại trường hợp sử dụng nào mà bạn thường sử dụng kỹ thuật này để giải quyết không? Bạn có lẽ nên đề cập đến nó trong câu hỏi của bạn để tránh [vấn đề XY] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Có lẽ lua có thể cung cấp một cách tiếp cận khác cho nó. – greatwolf

+0

Vì '__index' không được gọi nếu giá trị tồn tại trong một bảng, phần đầu tiên của hàm' __index' không sử dụng. – dualed

Trả lời

2

Bạn đã xác định được sự cố tốt: không, theo như tôi biết, có thể giải quyết vấn đề đó trong Lua thuần túy.

EDIT: Tôi đã sai, bạn có thể thực hiện bằng cách thực hiện nil có thể gọi. Xem các câu trả lời khác. Nó vẫn là một ý tưởng tồi IMO. Trường hợp sử dụng chính cho method_missing là các đối tượng proxy và bạn có thể giải quyết điều đó theo cách khác. method_missing trên Kernel (Ruby)/_G (Lua) là khủng khiếp :)

Những gì bạn có thể làm là chỉ xử lý một số phương pháp, ví dụ nếu bạn biết bạn mong đợi phương pháp mà bắt đầu bằng cách 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) 

Bây giờ có lẽ câu hỏi nên là: tại sao bạn muốn sao chép method_missing và bạn có thể tránh không? Ngay cả trong Ruby, khuyên tránh sử dụng method_missing và thích tạo phương thức động khi có thể.

3

Bạn có thể tránh sự cố này bằng cách thực hiện nil giá trị có thể gọi.
Không may, điều này có thể được thực hiện chỉ từ mã máy chủ (ví dụ: chương trình C), không phải từ tập lệnh Lua.

mã 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; 

Output:

true 
This method is under construction 
+5

Bạn có thể sử dụng 'debug.setmetatable (nil, {__ call = f})', – lhf

+0

Aha! Tôi tin rằng sẽ đủ để cho nó hoạt động như mong đợi, mặc dù có thể có một số hậu quả không mong muốn ... –

+0

@WesleyWigham Điều này có vẻ như đường cú pháp. Tại sao không chỉ sử dụng 'rawget' để làm kiểm tra mà tránh sự cần thiết cho hack khó khăn này hoàn toàn? Bạn có thể đặt nó trong bàn hoặc có thể so sánh được để dễ sử dụng hơn. ví dụ. Việc sử dụng cú pháp trở thành '_G: tồn tại" this_should_be_nil "' thay vì '_G.this_should_be_nil' – greatwolf

1

Vì vậy, với các tip từ @lhf, tôi đã quản lý một khá gấp đôi (từ những gì tôi có thể nói) của method_missing.Cuối cùng, tôi đã phát triển như sau:

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? 

Output:

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 

đoạn này cho phép bạn sử dụng method_missing khá tương tự như thế nào bạn có thể trong Ruby (với việc kiểm tra không có phản ứng gì, mặc dù). Nó tương tự như phản ứng ban đầu của tôi, ngoại trừ nó 'vượt qua buck' thông qua metil của nil, một cái gì đó tôi nghĩ rằng tôi không thể làm. (cảm ơn cho tip!) Nhưng như @greatwolf nói, có lẽ không có lý do để bao giờ sử dụng một cấu trúc như thế này trong Lua; cùng một động lực có thể đạt được thông qua các thao tác metamethod rõ ràng hơn.

Các vấn đề liên quan