Lua does not provide any explicit mechanism for packages. However, we can easily implement them using the basic mechanisms that the language provides. Actually, there are several ways to do that, and that creates a problem: There is no standard way to write a package in Lua. Moreover, it is up to you to follow the rules; there is neither a fixed way to implement packages, nor fixed operations to manipulate them.
lua
.)
Despite its naivety,
this is a quite satisfactory solution
(at least it did not stop people from using C in huge projects).
In Lua, a better solution is to implement packages with tables:
We only have to put our identifiers as keys in a table,
instead of as global variables.
The main point here is that we can store functions inside a table,
just as any other value.
For instance, suppose we are writing a library to manipulate
complex numbers.
We represent each number as a table,
with fields r
(real part) and i
(imaginary part).
To avoid polluting the global namespace,
we will declare all our new operations in a table
that acts as a new package:
Complex = {} Complex.i = {r=0, i=1} function Complex.new (r, i) return {r=r, i=i} end function Complex.add (c1, c2) return {r=c1.r+c2.r, i=c1.i+c2.i} end function Complex.sub (c1, c2) return {r=c1.r-c2.r, i=c1.i-c2.i} end function Complex.mul (c1, c2) return {r = c1.r*c2.r - c1.i*c2.i, i = c1.r*c2.i + c1.i*c2.r} end function Complex.inv (c) local n = c.r^2 + c.i^2 return {r=c.r/n, i=c.i/n} end
With this definition, we can use any complex operation qualifying the operation name, like this:
c = Complex.add(Complex.i, Complex.new(10, 20))
The use of tables for packages does not provide exactly the same
functionality as provided by real packages.
In Lua, we must explicitly put the package name in every function
definition.
Moreover, a function that calls another function
inside the same package must qualify the name of the called function.
We can ameliorate those problems using a fixed local name
for the package (Public
, for instance),
and then assigning this local to the final name of the package.
Following this guideline, we would write our previous definition like
this:
local Public = {} Complex = Public -- package name Public.i = {r=0, i=1} function Public.new (r, i) return {r=r, i=i} end ...Whenever a function calls another function inside the same package (or whenever it calls itself recursively), it should access the called function through an upvalue of the local name of the package. For instance:
function Public.div (c1, c2) return %Public.mul(c1, %Public.inv(c2)) endFollowing these guidelines, the connection between the two functions does not depend on the package name. Moreover, there is only one place in the whole package where we write the package name.
Usually, all names inside a package are exported; that is, they can be used by any client of the package. Sometimes, however, it is useful to have private names in a package, that is, names that only the package itself can use. A convenient way to do that is to define another local table for the private names in a package. That way, we distribute a package in two tables, one for public and the other for private names. Because we assign the public table to a global variable (the package name), all its components are accessible from the outside. But as we do not assign the private table to any global variable, it remains locked inside the package. To illustrate this technique, let us add to our example a private function that checks whether a value is a valid complex number. Our example now looks like this:
local Public, Private = {}, {} Complex = Public function Private.checkComplex (c) assert((type(c) == "table") and tonumber(c.r) and tonumber(c.i), "bad complex number") end function Public.add (c1, c2) %Private.checkComplex(c1); %Private.checkComplex(c2); return {r=c1.r+c2.r, i=c1.i+c2.i} end ...
So, what are the pros and cons of this approach?
All names in a package live in a separate namespace.
Each entity in a package is clearly marked as public or private.
Moreover, we have real privacy:
Private entities are inaccessible outside the package.
The main drawback of this approach is its verbosity when accessing
other entities inside the same package:
Every access needs a prefix (%Public.
or %Private.
).
Despite the verbosity, these accesses are quite efficient;
and we can mitigate this verbosity by providing shorter aliases
for these two variables
(with something like local E, I = Public, Private
).
There is also the problem that we have to change the prefixes whenever
we change the status of a function between public and private.
Nevertheless, I like this approach overall.
For me, the negative side (its verbosity) is more than paid for by
the simplicity of the language.
After all, we can implement a quite satisfactory package system
without needing any extra feature from the language.
An obvious benefit of using tables to implement packages is that we can manipulate packages like any other table, and use the whole power of Lua to create extra facilities. There are endless possibilities. Here we 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 ... end
block,
so that its Public
and Private
variables are restricted to
that block.
If we are going to use some operations often, we can give them global (or local) names:
add = Complex.add local i = Complex.i c1 = add(Complex.new(10, 20), i)Or else, if we do not want to write the whole package name over and over, we can give a shorter local name to the whole package at once:
local C = Complex c1 = C.add(C.new(10, 20), C.i)
It is easy to write a function that opens the whole package, putting all its names in the global namespace:
function openpackage (ns) for n,v in ns do setglobal(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 ns do if getglobal(n) ~= nil then error(format("name clash: `%s' is already defined", n)) end setglobal(n,v) end end
Because packages themselves are tables, we can even nest packages; that is, we can create a whole package inside another one. However, such facility is seldom necessary.
Typically, when we write a package,
we put its whole code in a single file.
Then, to open or import a package
(that is, to make it available) we just execute that file.
For instance, if we have a file complex.lua
with the
definition of our complex package,
the command dofile("complex.lua")
will open the package.
To avoid waste when a package is loaded multiple times,
we can start a package checking whether it is already loaded:
if Complex then return end local Public, Private = {}, {} Complex = Public ...Now, if you run
dofile("complex.lua")
when
Complex
is already defined,
the whole file is skipped.
(Notice: the new function require
,
to be available in Lua 4.1, will turn this check obsolete.)