Bound Scalar Globals One

lua-users home
wiki

The problem: how can one define a global variable such that accessing and setting its value actually calls a C function which manipulates an internal object. This is easy in Lua 4 but Lua 5 lacks most of the metamethods which would be used for a naive Lua 4 implementation.

Here is one solution, which requires that the globals be bound by name. See BoundScalarGlobalsTwo for a different implementation.

The idea here is simple: the bound scalars are not present in the globals table; instead, getter and setter functions are placed into two other tables, and these functions are called by the global table's __index and __newindex metamethods. This has no performance impact on global variables which have values, and hopefully reasonably limited impact on bound variables. (Proxied would be a better name, probably -- the phrase "bound scalar" comes from Perl. [1])

Here, I take care to use any existing __index and __newindex metamethods from the existing globals table. Hopefully, other existing metamethods don't matter, but they could be handled too.

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

Sample output:


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

Footnotes:

[1] In Perl, these are formally called [tied variables] (see also [Cultured Perl: Tied variables]), which are defined as a type of binding. You're tying (binding) the identity (name or address) of a variable to a particular implementation defined in a class. This class could be considered a proxy to something else, but there may be something to be said about the binding of the proxy to an identifier in the language, which may require special language support. See [Wikipedia Binding_(computer_science)] (particularly name binding) and [Wikipedia Proxy Pattern]. See also the term as in [Wikiepdia Free variables and bound variables]. In particular, Perl's TIEHASH and TIEARRAY are somewhat analogous to Lua's metatable mechanism. Perl's TIESCALAR seems to have no such analog in Lua, but a similar effect, as described in this page, is to modify the metatable of the global environment (possibly a disadvantage unless some general framework as given above is developed to prevent clashes). --DavidManura

This has only one problem: __newindex is only called on values that were not set before, e.g. x = "bla"; x = "more bla" will call __newindex only once. --Anon

Yes, that's why it doesn't actually set the value if it users the setter function. However, when you create the setter functions, you must first ensure that the corresponding global is not set; if you want to add a setter function later on, the same thing applies. Give it a try :) --RiciLake


RecentChanges · preferences
edit · history
Last edited March 22, 2007 10:52 pm GMT (diff)