Beginning Lua Programming (2007)
Chapter 7. Using Modules
One of the most important techniques of structured programming is partitioning a program into subprograms that have well-defined interfaces and protected implementations. In this chapter, you learn how to do the following:
· Work with interfaces and implementations
· Modularize Lua scripts
· Use Lua modules from your application scripts
As a working example, you’ll explore a Lua value inspection module that should prove useful when you develop Lua programs.
Interfaces and Implementations
An interface is the part of a subprogram that is known to callers of the subprogram. The implementation refers to the way the subprogram works internally. If a subprogram’s interface is clearly defined and its implementation doesn’t depend on variables that can be modified by other program parts, then it has the desirable attribute of being reusable. This discipline is often referred to as encapsulation or information hiding or modularization. You’ve seen already how functions in Lua present an interface behind which local variables are protected from tampering. A function is loosely coupled (and therefore is more easily used in a variety of contexts) to the extent that it minimizes the scope of the variables it uses.
These notions of interface and implementation extend to files containing Lua code. When such a file is loaded, Lua converts it into a function, to which arguments can be passed and from which results can be returned. In this way, a Lua file has its own interface and its own implementation.
A Lua module is simply a file that conforms to certain rules. First, it can be located and loaded at runtime. Second, it receives one argument: the name of the module. And third, it returns at most one result. The use of modules doesn’t guarantee reusability, but it certainly promotes this objective by providing a useful level at which to define an interface beneath which the actual workings can be hidden.
Libraries written in C and other low-level languages are conventionally made available through Lua’s module mechanism. You can also associate a namespace (a Lua table that contains the module’s functions and other fields) with a module. This practice lends clarity to programs and reduces the chance of name clashes.
The require Function
The principal high-level function that implements module conventions in Lua is require. The various lower-level functions it uses to actually locate, load, and run modules are all available to Lua programs. require simply codifies the way modules are handled to make the business of creating and using modules more uniform across applications.
Newcomers to Lua may confuse require with a directive, which is a direction to be heeded at compile-time rather than runtime. In particular, C programmers may initially make the mistake of thinking require acts like #include. Keep in mind that require is a function and that any modules that are required need to be available at runtime. Lua does not have any preprocessor or compiler directives.
Try It Out
Using Modules
There are a few interesting and clever subtleties to modules in Lua, but the basic operation is straightforward. Here’s an example that illustrates modules at their simplest. It demonstrates how you can package Lua code into a module so that it can be used by other scripts, and how a script makes use of a module.
1. Add a temporary variable to your shell environment. In Unix-type systems, issue this command:
export LUA_PATH='?.luac;?.lua'
2. In Windows, issue this command from a shell prompt:
set LUA_PATH=?.luac;?.lua
3. With a text editor, create a new file and add the following lines to it:
function ObjectShow(Val, Key, TruncLen)
print(Key, Val)
end
4. Save this module file as show.lua.
5. Create another file with these contents:
require("show")
ObjectShow("My table", {A = 1})
6. Save this main script file as showtest.lua in the same directory as show.lua.
7. Run the script by executing the following command:
lua showtest.lua
The response looks something like this:
My table table: 00755610
How It Works
Lua’s response doesn’t appear especially spectacular, but a fair amount of processing occurs behind the scenes.
First, Lua starts up and transfers the contents of the shell environment variable LUA_PATHto its own variable named package.path.Your script can modify this variable, but in this example, you leave it alone.
The script calls Lua’s built-in require function with the name of the module you want to import, namely show.
(In many cases, the require call is followed by a string without parentheses. Remember that Lua treats a function followed by a literal string the same as if the string had parentheses around it.
After verifying that the show module has not already been loaded, the require function traverses, in order, the segments of the package.path variable that are delimited by semicolons. In this example, these segments are ?.luac and ?.lua. For each segment, the function performs a simple pattern substitution. It replaces each occurrence of the question mark with the name that was passed to it and checks to see if a file by that name exists. The first substitution results in the show.luac filename. Because the segment does not contain path information, the require function follows the usual shell semantics and looks in the current working directory for the file. It doesn’t find this file, so it moves onto the next segment. The second substitution results in the show.lua filename, which it is able to find.
The require function loads show.lua as a chunk and calls it with a single argument, the string "show" .
The execution of show.lua results in the formation of a new global variable, an ObjectShow function. Here, show.lua doesn’t return any value, but if it did, require would return it to the main script.
Where to Put Modules
You’ll want to create a directory to hold Lua extension modules, but you do not want that directory in the operating system’s search path, because you want the system to locate lua, and you want lua to locate its modules. Some of the modules are executable libraries that will be loaded and linked at runtime. These libraries are tailored specifically to Lua, and you don’t want the operating system to inadvertently load one or to even waste time looking through them.
The following section summarizes what you need to do to assure that Lua can find its modules. First, you create a directory for modules, and then you set an environment variable to be used by Lua to locate modules.
Creating a Module Directory
The locations shown here for Lua module directories are fairly standard among Lua users, but you may change them if you want to or if you lack sufficient system privileges to create them. On Unix-type systems, use a command like this:
mkdir -p /usr/local/lib/lua/5.1
Some installers of Lua use the directory /usr/local/share/lua/5.1 for Lua scripts and reserve /usr/local/lib/lua/5.1 for shared libraries that interface with Lua’s C API.
On Windows, issue commands like these to create the module directory:
mkdir"c:\programfiles\lua"
mkdir"c:\programfiles\lua\5.1"
Setting Lua’s Environment Variable
The LUA_PATH environment variable used in the preceding Try It Out example is rather simple—it doesn’t include any directory information. Change that now so that LUA_PATH takes into account the new directory you have created. On Unix-type systems, where filenames look like this:
/usr/local/lib/lua/5.1/show.lua
make the following assignment:
export LUA_PATH='?.lua;/usr/local/lib/lua/5.1/?.lua'
On Windows, where filenames that look like this:
C:\Program Files\lua\5.1\show.lua
make the following assignment:
set LUA_PATH=?.lua;C:\Program Files\lua\5.1\?.lua
If necessary, you can refer to Chapter 1 for instructions on setting environment variables. Note that on Windows, backslashes should not be escaped and names with spaces should not be quoted.
Regardless of the form that filenames take, the simple pattern substitution that Lua uses to locate modules is the same. The particular values for LUA_PATH shown previously specify that Lua is to look for Lua modules first in the current directory and then, if that fails, in the designated common directory.
Preserving a Module’s Interface
The preceding Try It Out example was trivial, but you will enhance it in the next Try It Out to provide many more details about the passed-in value. The revised module will prove to be very useful when you’re developing programs in Lua. In this example, you’ll do the revision while preserving the module’s interface. That is, although you’ll change the implementation of show.lua dramatically, you will not change the part that it exposes to the outside world, namely the ObjectShow function and its list of parameters. You’ll appreciate the importance of this when you realize that you don’t have to change showtest.lua in the slightest to benefit from the updated implementation of show.lua. This kind of encapsulation forms the basis of a well-designed application which is partitioned into a number of general-purpose, reusable modules.
Try It Out
Extending the show Module
In this Try It Out, you’ll dramatically enhance the show module to display more details about the value to be examined. In doing so, you’ll preserve the interface of the previous version.
1. Replace the contents of show.lua with the following:
-- This function conditions a key or value for display
local function LclRenderStr(Obj, TruncLen)
local TpStr = type(Obj)
if TpStr == "string" then
Obj = string.gsub(Obj, "[Λ%w%p ]", function(Ch)
return "\\" .. string.format("%03d", string.byte(Ch)) end )
if TruncLen and TruncLen > 0 and string.len(Obj) > TruncLen + 3 then
-- This could misleadingly truncate numeric escape value
Obj = string.sub(Obj, 1, TruncLen) ..
end
Obj = '"' .. Obj .. '"'
elseif TpStr == "boolean" then
Obj = "boolean: " .. tostring(Obj)
else
Obj = tostring(Obj)
end
return Obj
end
-- This function replaces ["x"]["y"] stubble with x.y. Keys are assumed to be —
-- identifier-compatible.
local function LclShave(Str)
local Count
Str, Count = string.gsub(Str, ,Λ%[%"(.+)%"%]$',
if Count == 1 then
Str = string.gsub(Str, '%"%]%[%"', '.')
end
return Str
end
local function LclRender(Tbl, Val, KeyStr, TruncLen, Lvl, Visited, KeyPathStr)
local VtpStr, ValStr
VtpStr = type(Val)
if Visited[Val] then
ValStr = "same as " .. Visited[Val]
else
ValStr = LclRenderStr(Val, TruncLen)
if VtpStr == "function" then -- Display function's environment
local Env = getfenv(Val)
Env = Visited[Env] or Env
ValStr = string.gsub(ValStr, "(function:%s*.*)$", "%1 (env " ..
string.gsub(tostring(Env), "table: ", "") .. ")")
elseif VtpStr == "table" then
ValStr = ValStr .. string.format(" (n = %d)", #Val)
end
end
KeyPathStr = KeyPathStr .. "[" .. KeyStr .. "]"
table.insert(Tbl, { Lvl, string.format('[%s] %s',
KeyStr, ValStr) })
if VtpStr == "table" and not Visited[Val] then
Visited[Val] = LclShave(KeyPathStr)
local SrtTbl = {}
for K, V in pairs(Val) do
table.insert(SrtTbl, { LclRenderStr(K, TruncLen), V, K, type(K) })
end
local function LclCmp(A, B)
local Cmp
local Ta, Tb = A[4], B[4]
if Ta == "number" then
if Tb == "number" then
Cmp = A[3] < B[3]
else
Cmp = true -- Numbers appear first
end
else -- A is not a number
if Tb == "number" then
Cmp = false -- Numbers appear first
else
Cmp = A[1] < B[1]
end
end
return Cmp
end
table.sort(SrtTbl, LclCmp)
for J, Rec in ipairs(SrtTbl) do
LclRender(Tbl, Rec[2], Rec[1], TruncLen, Lvl + 1, Visited, KeyPathStr)
end
end
end
-- This function appends a series of records of the form { level,
-- description_string } to the indexed table specified by Tbl. When this
-- function returns, Tbl can be used to inspect the Lua object specified by-- Val. Key specifies the name of the object. TruncLen specifies the maximum
-- length of each description string; if this value is zero, no truncation will
-- take place. Keys are sorted natively (that is, numbers are sorted
-- numerically and everything else lexically). String values are displayed with
-- quotes, numbers are unadorned, and all other values have an identifying -- prefix such as "boolean". Consequently, all keys are displayed within their
-- type partition. This function returns nothing; its only effect is to augment
-- Tbl.
function ObjectDescribe(Tbl, Val, Key, TruncLen)
LclRender(Tbl, Val, LclRenderStr(Key, TruncLen), TruncLen or 0, 1, {}, "")
end
-- This function prints a hierarchical summary of the object specified by Val
-- to standard out. See ObjectDescribe for more details.
function ObjectShow(Val, Key, TruncLen)
local Tbl = {}
ObjectDescribe(Tbl, Val, Key, TruncLen)
for J, Rec in ipairs(Tbl)do
io.write(string.rep(" ", Rec[1] - 1), Rec[2], "\n")
end
end
2. Save the file in the module directory you created earlier. This will be the show module’s permanent home.
3. In order to avoid some possible confusion, delete the older version show.lua in your current working directory. Now when you run showtest.lua, you’ll be presented with a richer display of the table you pass to ObjectShow.
4. Take a look at the entire Lua global environment as follows:
require("show")
ObjectShow(_G, "_G")
The output will span more lines than can fit on most screens, so you may want to pipe the output through a paging filter:
lua showtest.lua | more
On Linux, the wryly named less filter will provide more scrolling and search options.
How It Works
Being able to display data structures this way is indispensable when you’re developing scripts. After you have installed show.lua in a location included in the LUA_PATH sequence, other scripts you write can make use of it by simply requiring it.
The Lua interpreter enables you to specify a required module on the command line using the -l switch. For example, you view the global environment from your shell prompt as follows:
lua -l show -e "ObjectShow(_G, '_G')" | more
This Try It Out illustrates Lua’s module location and loading logic. You follow steps that are similar to the previous Try It Out, but now show.lua resides in a “permanent" location where you can put other Lua modules.
This example also illustrates the concept of interface preservation. It can be argued that this particular script’s interface could be improved, and you’ll soon learn techniques to do so, but the advantage of preserving a module’s interface should be clear.
There are some programming techniques that this example demonstrates as well. In particular, show.lua addresses the tree structure of Lua tables. Trees are special because any subtree (any table within a containing table) s a tree itself. A function that processes a tree can be used to process a subtree. This presents a classic application of using functions recursively.
Also note the use of thin wrappers at the bottom of the script. The ObjectShow function in the revised show.lua is simply a wrapper around the ObjectDescribe function. ObjectShow writes to the standard output stream, but ObjectDescribe and the functions it calls do not. This means you can useObjectDescribe in a wider variety of contexts than ObjectShow. For example, you can call ObjectDescribe by a web page generator or a graph generator. If a wrapper around ObjectDescribe required the services of some other module, such as a graphing library, it would make sense to place this wrapper in its own module. Requiring such a module directly in show.lua where it isn’t generally needed would diminish the reusability of show.lua.
Module Bookkeeping
A module can in turn require modules of its own. This sets up the scenario in which the same script might be required more than once. For example, you may require two modules, each of which requires the same third module. How does Lua handle situations like this? As is usual with Lua, a short example can provide all the insight you need to figure things out. With your text editor, create a file named modload.luawith the following contents:
print(... , package.loaded[...])
Serial = (Serial or 0) + 1
return Serial
Notice the use of the three dots (the vararg expression) as a placeholder for the module name. Remember that when Lua loads a chunk, it converts the chunk to a function. As with any Lua function, any practical number of arguments of any type can be passed to it. In this case, however, Lua has no way of knowing what arguments might be expected by the embedded chunk, so the arguments are passed in using the vararg mechanism. require passes only the module name to a module, so the vararg expression evaluates to a single string.
In the same directory, create a file named modloadtest.lua with the following contents:
local ModStr = "modload"
print("Main, before require", package.loaded[ModStr])
local Val = require(ModStr)
print("Main, after 1st require", package.loaded[ModStr], Val)
Val = require(ModStr)
print("Main, after 2nd require", package.loaded[ModStr], Val)
package.loaded[ModStr] = nil
Val = require(ModStr)
print("Main, after reset", package.loaded[ModStr], Val)
Now run the module like this:
lua modloadtest.lua
The output will look like the following:
Main, before require nil
modload userdata: 6731F6D0
Main, after 1st require 1 1
Main, after 2nd require 1 1
modload userdata: 6731F6D0
Main, after reset 2 2
Lua uses the table package.loaded to associate the name of a module with its return value. If the entry for a particular module is nil , it generally means that the module has not yet been loaded. As you can see, it might also mean that the table field has been cleared by your script to force a reload of the module. The userdata is Lua’s way of indicating that a module is in the process of being loaded and no return value is available. This enables Lua to recognize a recursive loading loop and raise an error. The use of a userdata for this purpose is quintessential Lua, because it’s a value that simply cannot be mistaken for the return value of a module.
Note that the require function did not load the module when it was called a second time. The package.loaded example value indicated that the module was already loaded, so require simply returned the value associated with it. This behavior suggests that you should do no more than define functions and initialize variables when the module chunk is called.
On systems such as Windows, filenames are case-insensitive. For example, Lib.lua is considered to be the same file as lib.lua. However, the associative table lookup mechanism that Lua uses to detect whether a file has already been loaded is case-sensitive. This can lead to a module being inadvertently loaded twice if somewhere in your application you call require("Lib") and elsewhere you call require("lib") .
Experiment with little variations to the modload.lua module to see how Lua responds. For example:
· Return nil rather than Serial.
· Require modload from within modload.lua.
· Make Serial a local variable instead of a global.
Bytecode
In the first Try It Out of this chapter, you saw the extension .luac in the LUA_PATH environment variable. This is the extension conventionally given to Lua bytecode, which is code that has been generated by the Lua compiler for subsequent use in the Lua virtual machine. Lua’s script loading mechanism can read both Lua source code and Lua bytecode. When it reads a chunk, it distinguishes between the two forms and in the case of bytecode, it dispenses with the compiling stage. You can use the standalone version of the Lua compiler, named luac or luac.exe depending on your platform, to generate a bytecode file. The topic of bytecode fits into a discussion of modules because a compiled Lua script can be placed in the module directory.
Why would you want to execute bytecode rather than source code? Actually, for fewer reasons than you might at first suppose.
In general, the platform and the Lua version of both the Lua compiler and the Lua interpreter must match for bytecode to be executed properly. The instruction set used by Lua’s virtual machine has changed over time. All bets are off when you compile Lua programs on one machine and run the resulting bytecode on another. Even if the two systems are running on similar hardware, it is likely that sooner or later there will be a version discrepancy between the compiler on one machine and the interpreter on another.
Lua has such a fast compiler that, in general practice, little performance is gained by skipping the compiling step. If you need to execute a Lua script repeatedly, it makes sense to load and compile the file once and then repeatedly call the resulting function.
The Lua compiler has a very nice feature that lets you amalgamate more than one Lua script into one bytecode file. To do this, simply specify the source files on the luac command line. This cannot be accomplished by using the require function, because even with a compiled script a call to requireoccurs at runtime. In fact, if you want to produce a single bytecode file that embeds Lua modules rather than loads them at runtime, you should include the modules on the luac command line and comment out calls to require.
If you’re looking for top-notch performance of Lua programs on the x86 platform, investigate LuaJIT, a beautifully implemented just-in-time compiler for Lua programs created by Mike Pall. The LuaJIT package includes a replacement for lua called luajit that compiles Lua bytecode into x86 machine code as a program runs. You can find it at luaforge.net.
Namespaces
Consider the following Util.lua module:
function Quote(Str)
return string.format("%q", Str)
end
Now look at the following UtilTest.lua test script, which requires Util.lua:
require("Util")
print(Quote('Natty "Pathfinder" Bumppo'))
Some programmers who use the Util module would be displeased that the variable Quote “pollutes" the global environment. In general, you would do well to model your approach after that used with the Lua libraries, and bundle your variables into a namespace table. For example, the string length function is given the name len in the global table named string. There are many ways you can implement a module with a namespace. The most common ways will be shown here, each implementing the Quote example in a slightly different manner.
Creating and Reusing Namespaces
Usually, a module will create a namespace table and place its functions and other variables in it. Here is the Quote example using this technique. The Util.lua module script contains the following:
Util = {}
function Util.Quote(Str)
return string.format("%q", Str)
end
return Util
The UtilTest.lua calling script contains this:
require("Util")
print(Util.Quote('Natty "Pathfinder" Bumppo'))
In this case, the namespace table Util is created as a global variable. By convention, it is also returned from the module. If more than one module is to add fields to the namespace table, it is important not to overwrite it. Find the following line:
Util = {}
And replace it with this:
Util = Util or {}
You need to do this in each module that adds fields to the Util table.
A module can add one or more fields into a standard library namespace. In this case, there is some merit in conforming to the naming conventions used in the namespace. Here is the Quote example applied to the standard string table (notice that the function is given an all-lowercase name). TheUtil.lua module script now contains the following:
function string.quote(Str)
return string.format("%q", Str)
end
return string
The UtilTest.lua test script contains this:
require("Util")
print(string.quote('Natty "Pathfinder" Bumppo1))
A module does not have to create any global variables. Here is the Quote example using a local namespace table. The Util.lua module script contains the following:
local Util = {}
function Util.Quote(Str)
return string.format("%q", Str)
end
return Util
The UtilTest.lua calling script contains this:
Utility = require("Util")
print(Utility.Quote('Natty "Pathfinder" Bumppo'))
In this case, the namespace table must be returned by the module; otherwise, it would not be accessible to the program which requires it. Of course, nothing prevents the requiring program itself from assigning the namespace table to a global variable.
Avoiding Global Variables
Good modularization technique requires that you channel all access to your module through its interface. Creating global variables in a module exposes a part of its implementation and, because these variables are subject to modification outside the module itself, increases the chance of hard-to-track bugs. What methods can you use to avoid inadvertently creating globals?
Using the strict Module
One technique is to use the strict.lua module, which is included in the etc directory of the Lua source distribution. Copy this file to the module directory you set up earlier in this chapter so that it can be loaded with a call to require. Here’s an example of its use:
require "strict"
local function Test()
A = 2
B = 3
end
A = 1
Test()
Running this script results in the following error message:
assign to undeclared variable 'B'
The assignment to A in function Test was okay, but the assignment to B was not. When using the strict module, global variables must be first assigned at the main level of the chunk. This is what the line does, which makes the subsequent reassignment of A in Test work fine:
A = 1
Reporting All Global Assignments
You can use the Lua compiler to list the virtual machine instructions of your program. Here’s an example of how to do this using the show.lua script:
luac -l -p show.lua
This results in a flurry of lines that look like the following:
6 [104] MOVE 8 2 0
7 [104] CALL 4 5 1
8 [105] GETGLOBAL 4 1 ; ipairs
9 [105] MOVE 5 3 0
10 [105] CALL 4 2 5
11 [105] TFORPREP 4 11 ; to 23
The SETGLOBAL instruction indicates the assignment of a global variable. You can simply filter out everything except these SETGLOBAL instructions using grep, like this:
luac -l -p show.lua | grep SETGLOBAL
On the Windows platform where grep isn’t available by default, you can try the following command:
luac -l -p show.lua | find "SETGLOBAL"
This generates the following output, which confirms that there are no global assignments other than the intended ones:
10 [95] SETGLOBAL 3 0 ; ObjectDescribe
12 [102] SETGLOBAL 3 1 ; ObjectShow
The module Function
You can modularize a Lua script by calling the module function. Calling this function simplifies the process of modularizing your Lua code by creating a namespace table and effectively placing all global variables into this table.
Try It Out
Using the module Function
1. Modify Util.lua so that it contains the following:
local format = string.format
module(... )
function Quote(Str)
return format("%q", Str)
end
2. The calling script UtilTest.lua contains:
require("Util")
print(Util.Quote('Natty "Pathfinder" Bumppo1))
3. Invoke the script in the usual way:
lua UtilTest.lua
The result is a quoted version of the specified string:
"Natty \"Pathfinder\" Bumppo"
How It Works
A number of things happen with the module function behind the scenes. Here is a summary of how it works:
1. The require function is called with the "Util ”string.
2. require resolves this by locating and loading "Util.lua" as a chunk and calling it with the single "Util" argument.
3. The first statement to be executed in the module is the assignment of string.format to the local variable format. This is needed because the subsequent call to module will make all global variables inaccessible. Recall that Lua resolves references to global variables (that is, variables that are not introduced with the local keyword) in the global environment. By default, every function and chunk is associated with a common global environment, but you can change this programmatically.
4. module is called with the "Util" string because, in this context, the vararg expression ... holds everything that the chunk itself received when it was called by the require function.
5. module creates a namespace table with the name Util in the current global environment.
6. module changes the environment of the chunk to this newly created namespace table.
7. After module returns, the chunk creates the Quote function and gives it the name Quote in its global environment. Because of the machinations of module, this is not the original environment—it’s the table that has the name Util in the original environment.
8. The string formatting function is called. However, Lua is now resolving all global variables in the new namespace table, which doesn’t contain string.format, so the local variable that points to this function has to be used.
9. require returns with the net effect that a new global variable named Util has been created.
It would be tedious if, before calling module, you needed to make a local copy of every global variable that you were going to access. You could instead make a local copy of the namespaces you were going to use, like this:
local string = string
local table = table
Lua allows you to take this to the next step, and make a local copy of the entire global environment. To do this, you add the following to the Util.lua file:
local _G = _G
module(... )
function Quote(Str)
return _G.string.format("%q", Str)
end
Now you can access the members of the original environment within the local table _G.
Alternatively, you can dispense with the local copy altogether and arrange to have Lua look for global variables in the original environment by means of a metatable. (Metatables and metamethods will be explained in Chapter 11.) The most convenient way to set this up is to pass the functionpackage.seeall as a second argument to module, like this:
module(..., package.seeall)
function Quote(Str)
return string.format("%q", Str)
end
The arguments to module can include, after the module name, zero or more functions. After module creates the new namespace table, it calls each of these functions in turn with the namespace table as an argument. package.seeall creates a metatable for the namespace table that is passed to it, and sets the index metamethod to look for missing fields in the global environment. In the previous example, string is missing from the module’s global environment, so Lua successfully looks for it in the original environment.
C Modules
Lua modules are not restricted to Lua scripts. One of the defining aspects of Lua is its ability to interoperate smoothly with bindings written in C or some other low-level language. Bindings of this type must be built to conform with platform-dependent library requirements and with Lua’s C programming interface. This feature allows Lua to act as an intermediary between the scripts you write and a vast collection of libraries that perform tasks such as network communication, document preparation, and user interface management. This topic is discussed in more detail in Chapters 9and 13.
If the require function fails to locate a matching file using LUA_PATH, it uses a similar approach to locate a compiled library. When searching for a library, require employs the same pattern substitution method as before but uses the LUA_CPATH environment variable. If it finds a match, it calls on the operating system to load the library. If this succeeds, it then locates and calls the function with the name formed by prepending luaopen_ to the name of the module you are requiring. The require function has additional nuances which allow you to bundle submodules into a module and to accommodate versioned modules; these techniques are described in the Lua manual.
Summary
In this chapter, you’ve learned about the following:
· Program modularization
· The importance of adhering to an established module interface
· The possibility of modifying a module’s implementation without breaking the programs that use it
· Lua’s support for modules using the require and module functions
· Lua’s method of locating and loading modules
Modules form the basis of a large number of Lua libraries and community libraries, which are discussed in the proceeding chapters. But before you go on, try out the concepts you learned in this chapter in the following exercises.
Exercises
1. In subsequent chapters, the show module will be used to provide insight into data structures managed by your programs. Convert the module into one that returns a table with the functions Describe and Show.
2. Imagine a Lua script named nomod.lua with the following contents:
error("cannot find module " .....)
Assuming there is no file named xyz.lua in the current directory, what happens when the following script is run?
package.path = "?.lua;nomod.lua"
require "xyz"
print("Here in test.lua")