H. Conrad Cunningham
19 September 2016
Note: I corrected a few formatting errors on 29 January 2020.
Acknowledgements: These slides are adapted from
my previous notes on modular development and data abstraction
slide set 9 for the course Programming in Lua created by Fabio Mascarenhas, Federal University of Rio de Janeiro, Brazil. It was presented at Nankai University, P. R. China, in July 2013.
Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of January 2020 is a recent version of Firefox from Mozilla.
So far using small scripts, mostly in REPL
Also using built-in functions such as ipairs
and table.concat
What if program gets big, uses many new functions?
Organize program into parts that work together – into modules
But how should we partition program?
“a work assignment given to a programmer or group of programmers” (Parnas, 1978) – software engineering concept
also may be program unit defined with construct or convention – programming language concept
ideally language feature supports software engineering method
enable programmers to understand the system by focusing on one module at a time (comprehensibility)
shorten development time by minimizing required communication among groups (independent development)
make system flexible by limiting number of modules affected by significant changes (changeability)
Forms cohesive unit of functionality separate from other modules
Hides a design decision (its secret) from other modules
Encapsulates aspect of system likely to change (its secret)
Aspects likely to change independently become secrets of separate modules
Aspects unlikely to change become interactions (connections) among modules
Interface:
“set of assumptions … each programmer needs to make about the other program … to demonstrate the correctness of his own program” (Britton, Parker, & Parnas, 1981)
includes function signatures (name, arguments, return)
includes constraints on environment and argument values (preconditions, postconditions, invariants)
Abstract interface:
Does not change when one module implementation substituted for another
concentrates on module’s essential aspects and obscures incidental aspects that vary among implementations
Information hiding with abstract interfaces supports software reuse
Tradeoff conflicts among criteria: completeness vs. simplicity, reusability vs. simplicity, convenience vs. { consistency, simplciity, no redundancy, atomicity}
Tradeoff criteria against efficiency and functionality
Represented by a Lua table – isn’t everything?!
Creates and returns the table from executing chunk
Normally defined in a separate file
Populates the table with functions and data structures to be exported
Carries private features only in exported function closures
Design should use information hiding and abstract interfaces
All variable and function names are Lua local
variables
Module only exports names of features users need
Module does explicit return
of its table
Function documents its assumptions (preconditions, postconditions, invariants); checks preconditions where feasible
Lua standard library modules: table
, io
, string
, math
, os
, etc.
Standard modules preloaded into global variables of same name
Program loads other modules with built-in require
function, assigning module’s table to (local) variable
Can develop stub for complex number module, store in file complex.lua
local M = {} -- create the module's table
function M.new(r, i) -- could be: M.new = function(r, i)
return { real = r or 0, im = i or 0 }
end
M.i = M.new(0, 1)
function M.add(c1, c2)
return M.new(c1.real + c2.real, c1.im + c2.im)
end
function M.tostring(c)
return tostring(c.real) .. "+" .. tostring(c.im) .. "i"
end
return M -- table with closures for keys "new", "add", "tostring"
-- also constant value for "i"
Performs better but duplicates code. (Instructor uses)
local function new(r, i)
return { real = r or 0, im = i or 0 }
end
local i = new(0, 1) -- call local function
local function add(c1, c2)
return new(c1.real + c2.real, c1.im + c2.im)
end
local function tos(c)
return tostring(c.real) .. "+" .. tostring(c.im) .. "i"
end
-- module table, "export" list, export name changed for tos
return { new = new, i = i, add = add, tostring = tos }
Can load using REPL
> complex = require "complex"
> print(complex)
table: 0000000000439820
Use cached module subsequently
> print(require "complex")
table: 0000000000439820
Can force reload
> package.loaded.complex = nil
> complex = require "complex"
> print(complex)
table: 000000000042F8F0
Load a module and assign to variable
Can use anything exported by module
> c1 = complex.new(1, 2)
> print(complex.tostring(c1))
1+2i
> c2 = complex.add(c1, complex.new(10,2))
> print(complex.tostring(c2))
11+4i
> c3 = complex.add(c2, complex.i)
> print(complex.tostring(c3))
11+5i
Could assign to module table’s fields, but bad style!
Where does require
get modules?
Uses search path in package.path
Will normally include current directory, which is acceptable for this course
Check documentation if different approach needed