bound scalar globals one |
|
這裡有一個解決方法,需要以名稱方式繫結全域變數,請參閱 BoundScalarGlobalsTwo 來取得不同的實作。
這裡的想法很簡單:bound scalars 不存在於全域表格中,但 getter 和 setter 函式放在另外兩個表格中,而且這些函式是由全域表格的 __index 和 __newindex 呼叫的。這對具有值的全域變數不會造成效能影響,而且很希望對繫結變數的影響相當有限。(代理可能是更好的名稱,短語「bound scalar」來自 Perl。[1])
這裡,我謹慎地使用現有的來自現有全域表格的 __index 和 __newindex metatable。這些其他現有 metamethods 最好不重要,但它們也能被處理。
-- BindScalar1 -- Version 0.2 -- RiciLake -- -- Change history -- -------------- -- Fixed the case where the globals table didn't have a metatable -- Corrected the behaviour on set where there was no existing -- __newindex metamethod so that it now rawsets the table -- Check to see if the old __index method is not a function -- to mimic the default behaviour -- Wrote a couple of quick example getters and setters -- Actually made sure it compiles and runs -- -- TODO -- ---- -- Actually debug it with real metatables -- Think of a setter that lets you set something -- -- BUGS -- ---- -- If you specify a getter and don't specify a setter, the binding stops -- working. It should be necessary to specify both. -- -- The API needs to be improved do local meta, getters, setters = {}, {}, {} local old_meta = getmetatable(getfenv()) local old_index, old_newindex if old_meta then old_index, old_newindex = old_meta.__index, old_meta.__newindex end -- at this point you have to populate the getters and setters table -- somehow, probably by getting them from your C code. -- Here is an example without C: -- the getter receives the name of the global as an argument local function get_time(k) if k == "gmt" then return os.date("!%c") else return os.date("%c") end end -- the setter actually receives the name and the proposed value -- but in this example we don't need them. local function set_time() error "You cannot change the time" end -- now put them into getters and setters. There should probably -- be a function to do that, something like: -- bind_scalar("now", get_time, set_time) getters.now = get_time getters.gmt = get_time setters.now = set_time setters.gmt = set_time -- Another example. Particular environment variables are made -- into globals. (Change this to USERNAME for Windows NT.) local function get_env(k) return os.getenv(k) end local function set_env(k, v) if os.setenv then os.setenv(k, v) else error "You cannot change environment variables on this platform." end end getters.USER = get_env setters.USER = set_env -- hmm? it's just an example -- It might be nice to change the calls below to object calls, -- such as getters[k](getters[k], k) -- For efficiency, you probably only want to do that lookup once. -- Here is the actual implementation of the metamethods. meta = {} if type(old_index) == "function" then function meta.__index(t, k) if getters[k] then return getters[k](k) else return old_index(t, k) end end elseif type(old_index) == "nil" then function meta.__index(t, k) if getters[k] then return getters[k](k) end end else function meta.__index(t, k) if getters[k] then return getters[k](k) else return old_index[k] end end end if old_newindex then function meta.__newindex(t, k, v) if setters[k] then setters[k](k, v) else old_newindex(t, k, v) end end else function meta.__newindex(t, k, v) if setters[k] then setters[k](k, v) else rawset(t, k, v) end end end setmetatable(getfenv(), meta) end
範例輸出
-- now is deferred to a function. > print(now) Thu Jan 16 12:34:40 2003 -- so is gmt > print(gmt) Thu Jan 16 17:35:34 2003 -- setting "works"; the variable is read-only > now = "tomorrow" glue.lua:27: You cannot change the time stack traceback: [C]: in function `error' glue.lua:27: in function `?' glue.lua:91: in function <glue.lua:88> stdin:1: in main chunk [C]:[C] -- This mechanism might be useful in a CGI script, for example > print(USER) rlake -- Most platforms implement setenv but it's not ANSI standard. This -- would work if you patched the os library. > USER="root" glue.lua:44: You cannot change environment variables on this platform. stack traceback: [C]: in function `error' glue.lua:44: in function `?' glue.lua:91: in function <glue.lua:88> stdin:1: in main chunk [C]:[C] -- Ordinary globals continue to be ordinary. > print(j) nil > j = 7 > print(j) 7
腳註
這只有一個問題:__newindex 只在未曾設定的值上呼叫,例如 x = "bla"; x = "more bla" 將只呼叫 __newindex 一次。 -- 匿名人士
是的,這就是它實際上沒有設定值,如果它使用 setter 函式的話。但當您建立 setter 函式時,您必須先確保對應的全域變數沒有設定;如果您想要之後加入 setter 函式,則適用同樣的方式。試試看吧 :) --RiciLake