H. Conrad Cunningham
19 September 2016
Acknowledgements: These slides are adapted from the slide set 6 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. Although that course used Lua 5.2, I have attempted to update these slides to Lua 5.3.
Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of September 2016 is a recent version of Firefox from Mozilla.
...
Can collect extra arguments using ...
in table constructor
function add(...) -- variadic function
local sum = 0
for _, n in ipairs({ ... }) do -- iterate over args
sum = sum + n
end
return sum
end
If any extra arguments nil
then { ... }
not array
Can use table.pack
function to collect arguments in table
> t = table.pack(1, nil, 3) -- n set to number of arguments
> for i = 1, t.n do print(t[i]) end
1
nil
3
table.unpack
table.unpack
returns all elements of array in order (called just unpack
in Lua 5.1 and earlier)
> print(table.unpack{ 1, 2, 3, 4 })
1 2 3 4
Only guaranteed to work for proper arrays (without holes)
table.unpack
optionally takes starting and ending indices and returns all elements in interval regardless of holes
> a = { [2] = 5, [5] = 0 }
> print(table.unpack(a, 1, 5))
nil 5 nil nil 0
Can simulate named arguments with a record argument
function rename(args)
return os.rename(args.old, args.new)
end
Can omit parentheses if function call has single record argument and uses table constructor {}
rename{ new = "perm.lua", old = "temp.lua" }
Style note: Can put spaces between function and {
but better to omit
Any local variable visible at function definition also visible inside function (if not shadowed)
function derivative(f, dx)
dx = dx or 1e-4
return function (x)
-- both f and dx visible here!
return (f(x + dx) -f(x)) / dx
end
end
Higher-order function is function that takes/returns function(s)
derivative
is higher-order -- both takes & returns
> df = derivative(function (x) return x * x * x end)
> print(df(5))
75.001500009932
Function closes over all local variables in its lexical scope
These variables neither global nor local to function
Non-local variables called upvalues in Lua
Function value called closure
Function can access/modify non-local variables in its closure
function counter()
local n = 0
return function ()
n = n + 1 -- n non-local to returned function
return n
end
end
function counter()
local n = 0
return function ()
n = n + 1 -- n non-local to returned function
return n
end
end
Each counter()
call creates new closure with different n
> c1 = counter()
> c2 = counter()
> print(c1())
1
> print(c1())
2
> print(c2())
1
Closures close over the variables themselves, not copies
function counter2()
local n = 0
return function (x)
n = n + (x or 1) -- same non-local n
return n
end, -- returns two result values
function (x)
n = n - (x or 1) -- same non-local n
return n
end
end
> inc, dec = counter2()
> print(inc(5))
5
> print(dec(2))
3
> print(inc())
4
Only access to n
through the closures!
Can implement lightweight callbacks using closures
Example: table.sort
takes callback for optional second argument -- "less-than" predicate for elements
> a = { "Python", "Lua", "C", "JavaScript", "Java", "Lisp" }
> table.sort(a, function (a, b) return a > b end)
> print_array(a)
{ Python, Lua, Lisp, JavaScript, Java, C }
Common in GUI and other event-driven or asynchronous programs
Functional programming typically uses higher-order functions and immutable values
Lua primarily an imperative language with mutable data structures
Functional languages commonly use linked lists for sequences
Lua typically uses arrays for sequences
Lua can implement common functional programming patterns on arrays by returning modified copies
map
FunctionMap functions abstract a common pattern
Can implement map
function on Lua arrays
function map(f, l)
local nl = {} -- nl not accessible outside
for i, x in ipairs(l) do
nl[i] = f(x)
end
return nl
end
> a = { 1, 2, 3, 4, 5 }
> b = map(function (x) return x * x end, a)
> print_array(b)
{ 1, 4, 9, 16, 25 }
filter
FunctionFilter functions abstract another common pattern
Can implement filter
function on Lua arrays
function filter(p, l)
local nl = {} -- nl not accessible outside
for _, x in ipairs(l) do
if p(x) then
nl[#nl+1] = x
end
end
return nl
end
> a = { 1, 2, 3, 4, 5 }
> b = filter(function (x) return x % 2 == 1 end, a)
> print_array(b)
{ 1, 3, 5 }
Fold functions abstract another common pattern
Left fold applies operation to seed and first element, then applies operation to result and each subsequent element
Right fold applies operation to last element and seed, then applies the operation to each previous element and the result
Can implement foldl
(fold left) and foldr
(fold right) on Lua arrays
function foldl(op, z, l)
for _, x in ipairs(l) do
z = op(z, x) -- z not accessible outside
end
return z
end
function foldr(op, z, l)
for i = #l, 1, -1 do
z = op(l[i], z) -- z not accessible outside
end
return z
end
Take a proper prefix of arguments and return function that takes remaining arguments
Consider curried version of map
function map2(f)
return function(l)
local nl = {}
for i, x in ipairs(l) do
nl[i] = f(x)
end
return nl
end
end
function map2(f)
return function(l)
local nl = {}
for i, x in ipairs(l) do
nl[i] = f(x)
end
return nl
end
end
Currying enables partial evaluation of functions
> square = map2(function (x) return x * x end)
> print_array(square{ 1, 5, 9 })
{ 1, 25, 81 }
What is wrong with the function named below, that turns a function f
with positional arguments into a function with named arguments? How to fix it?
function named(f, names) -- names: names to position
return function (args) -- args: names to values
local l =
map(function (name) return args[name] end, names)
f(table.unpack(l))
end
end
rename = named(os.rename, { "old", "new" })
rename{ old = "old.txt", new = "new.txt" }
function named(f, names) -- names: name to position
return function (args) -- args: names to values
local l =
map(function (name) return args[name] end, names)
f(table.unpack(l))
-- return f(table.unpack(l,1,#names))
end
end
rename = named(os.rename, { "old", "new" })
rename{ old = "old.txt", new = "new.txt" }