This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.


15.5 – Other Facilities

As I said earlier, the use of tables to implement packages allows us to use the whole power of Lua to manipulate them. There are unlimited possibilities. Here I will give only a few suggestions.

We do not need to define all public items of a package together. For instance, we can add a new item to our complex package in a separate chunk:

    function complex.div (c1, c2)
      return complex.mul(c1, complex.inv(c2))
    end
(But notice that the private part is restricted to one file, which I think is a good thing.) Conversely, we can define more than one package in the same file. All we have to do is to enclose each one inside a do block, so that its local variables are restricted to that block.

Outside the package, if we are going to use some operations often, we can give them local names:

    local add, i = complex.add, complex.i
    
    c1 = add(complex.new(10, 20), i)
Or else, if we do not want to write the package name over and over, we can give a shorter local name to the package itself:
    local C = complex
    c1 = C.add(C.new(10, 20), C.i)

It is easy to write a function that unpacks a package, putting all its names into the global namespace:

    function openpackage (ns)
      for n,v in pairs(ns) do _G[n] = v end
    end
    
    openpackage(complex)
    c1 = mul(new(10, 20), i)
If you are afraid of name clashes when opening a package, you can check the name before the assignment:
    function openpackage (ns)
      for n,v in pairs(ns) do
        if _G[n] ~= nil then
          error("name clash: " .. n .. " is already defined")
        end
        _G[n] = v
      end
    end

Because packages themselves are tables, we can even nest packages; that is, we can create a package inside another one. However, this facility is seldom necessary.

Another interesting facility is autoload, which only loads a function if the function is actually used by the program. When we load an autoload package, it creates an empty table to represent the package and sets the __index metamethod of the table to do the autoload. Then, when we call any function that is not yet loaded, the __index metamethod is invoked to load it. Subsequent calls find the function already loaded; therefore, they do not activate the metamethod.

A simple way to implement autoload can be as follows. Each function is defined in an auxiliary file. (There can be more than one function in each file.) Each of these files defines its functions in a standard way, for instance like here:

    function pack1.foo ()
      ...
    end
    
    function pack1.goo ()
      ...
    end
However, the file does not create the package, because the package already exists when the function is loaded.

In the main package we define an auxiliary table that describes where we can find each function:

    local location = {
      foo = "/usr/local/lua/lib/pack1_1.lua",
      goo = "/usr/local/lua/lib/pack1_1.lua",
      foo1 = "/usr/local/lua/lib/pack1_2.lua",
      goo1 = "/usr/local/lua/lib/pack1_3.lua",
    }
Then we create the package and define its metamethod:
    pack1 = {}
    
    setmetatable(pack1, {__index = function (t, funcname)
      local file = location[funcname]
      if not file then
        error("package pack1 does not define " .. funcname)
      end
      assert(loadfile(file))()     -- load and run definition
      return t[funcname]           -- return the function
    end})
    
    return pack1
After loading this package, the first time the program executes pack1.foo() it will invoke that __index metamethod, which is quite simple. It checks that the function has a corresponding file and loads that file. The only subtlety is that it must not only load the file, but also return the function as the result of the access.

Because the entire system is written in Lua, it is easy to change its behavior. For instance, the functions may be defined in C, with the metamethod using loadlib to load them. Or we can set a metamethod in the global table to autoload entire packages. The possibilities are endless.