绑定标量全局变量一

lua-users home
wiki

问题:如何定义一个全局变量,使其访问和设置值实际上会调用一个C函数来操作内部对象?这在 Lua 4 中很容易,但 Lua 5 缺少大多数用于实现 Lua 4 的简单方法的元方法。

这里提供一个解决方案,它要求全局变量按名称绑定。有关不同实现,请参见绑定标量全局变量二

这里的想法很简单:绑定标量不在全局表中;相反,getter 和 setter 函数被放置在另外两个表中,并且这些函数由全局表的__index__newindex 元方法调用。这不会对具有值的全局变量产生性能影响,并且希望对绑定变量的影响也相当有限。(代理可能是一个更好的名称——“绑定标量”一词来自 Perl。[1])

在这里,我注意使用现有全局表中任何现有的__index__newindex 元方法。希望其他现有的元方法无关紧要,但也可以处理它们。

-- 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

脚注

[1] 在 Perl 中,这些正式被称为 [绑定变量](另请参阅 [文化 Perl:绑定变量]),它们被定义为一种绑定类型。您正在将变量的标识(名称或地址)绑定到类中定义的特定实现。这个类可以被认为是其他事物的代理,但关于代理与语言中标识符的绑定可能有一些需要特别语言支持的内容。请参阅 [维基百科绑定(计算机科学)](特别是名称绑定)和 [维基百科代理模式]。另请参阅 [维基百科自由变量和绑定变量] 中的术语。特别是,Perl 的 TIEHASH 和 TIEARRAY 与 Lua 的元表机制有些类似。Perl 的 TIESCALAR 在 Lua 中似乎没有类似物,但如本页所述,类似的效果是修改全局环境的元表(除非开发了如上所述的一般框架来防止冲突,否则可能是一个缺点)。--DavidManura

这只有一个问题:__newindex 仅在之前未设置的值上调用,例如 x = "bla";x = "more bla" 将仅调用 __newindex 一次。--Anon

是的,这就是为什么它在使用 setter 函数时实际上不会设置值的原因。但是,在创建 setter 函数时,您必须首先确保相应的全局变量未设置;如果您想稍后添加 setter 函数,同样的事情也适用。试试看 :) --RiciLake


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2007 年 3 月 22 日下午 11:52 GMT (差异)