Advanced Functions in Lua
Advanced function techniques unlock the full power of Lua's functional programming capabilities. In this lesson, you'll learn about higher-order functions, function composition, currying, and classic functional patterns like map, filter, and reduce. These techniques will help you write more elegant, reusable, and expressive code. Let's dive into advanced function mastery!
Higher-Order Functions
A higher-order function is a function that either:
- Takes one or more functions as arguments, OR
- Returns a function as its result
Functions as Arguments
local function apply(func, value)
return func(value)
end
local function double(x)
return x * 2
end
local function square(x)
return x * x
end
print(apply(double, 5)) -- 10
print(apply(square, 5)) -- 25
Functions as Return Values
local function createAdder(x)
return function(y)
return x + y
end
end
local add5 = createAdder(5)
local add10 = createAdder(10)
print(add5(3)) -- 8
print(add10(3)) -- 13
Click Run to execute your code
Map, Filter, and Reduce
These are fundamental functional programming patterns:
Map - Transform Each Element
local function map(array, func)
local result = {}
for i, v in ipairs(array) do
result[i] = func(v)
end
return result
end
local numbers = {1, 2, 3, 4, 5}
local doubled = map(numbers, function(x) return x * 2 end)
-- doubled = {2, 4, 6, 8, 10}
local squared = map(numbers, function(x) return x * x end)
-- squared = {1, 4, 9, 16, 25}
Filter - Select Elements
local function filter(array, predicate)
local result = {}
for i, v in ipairs(array) do
if predicate(v) then
table.insert(result, v)
end
end
return result
end
local numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
local evens = filter(numbers, function(x) return x % 2 == 0 end)
-- evens = {2, 4, 6, 8, 10}
local greaterThan5 = filter(numbers, function(x) return x > 5 end)
-- greaterThan5 = {6, 7, 8, 9, 10}
Reduce - Combine Elements
local function reduce(array, func, initial)
local accumulator = initial
for i, v in ipairs(array) do
accumulator = func(accumulator, v)
end
return accumulator
end
local numbers = {1, 2, 3, 4, 5}
-- Sum
local sum = reduce(numbers, function(acc, x) return acc + x end, 0)
print(sum) -- 15
-- Product
local product = reduce(numbers, function(acc, x) return acc * x end, 1)
print(product) -- 120
-- Max
local max = reduce(numbers, function(acc, x) return math.max(acc, x) end, numbers[1])
print(max) -- 5
Click Run to execute your code
Function Composition
Combine multiple functions into a single function:
local function compose(f, g)
return function(x)
return f(g(x))
end
end
local function double(x)
return x * 2
end
local function addOne(x)
return x + 1
end
-- Compose: first addOne, then double
local doubleAfterAddOne = compose(double, addOne)
print(doubleAfterAddOne(5)) -- 12 (5 + 1 = 6, 6 * 2 = 12)
-- Multiple composition
local function square(x)
return x * x
end
local complexFunc = compose(compose(square, double), addOne)
print(complexFunc(3)) -- 64 (3 + 1 = 4, 4 * 2 = 8, 8 * 8 = 64)
Pipe - Left-to-Right Composition
local function pipe(...)
local funcs = {...}
return function(x)
local result = x
for i, func in ipairs(funcs) do
result = func(result)
end
return result
end
end
-- More readable: addOne -> double -> square
local transform = pipe(addOne, double, square)
print(transform(3)) -- 64
Click Run to execute your code
Currying
Transform a function with multiple arguments into a sequence of functions:
-- Regular function
local function add(a, b, c)
return a + b + c
end
-- Curried version
local function curriedAdd(a)
return function(b)
return function(c)
return a + b + c
end
end
end
print(add(1, 2, 3)) -- 6
print(curriedAdd(1)(2)(3)) -- 6
-- Partial application with currying
local add1 = curriedAdd(1)
local add1and2 = add1(2)
print(add1and2(3)) -- 6
Generic Curry Function
local function curry(func, numArgs)
local function curried(args)
return function(x)
local newArgs = {table.unpack(args)}
table.insert(newArgs, x)
if #newArgs >= numArgs then
return func(table.unpack(newArgs))
else
return curried(newArgs)
end
end
end
return curried({})
end
local function multiply(a, b, c)
return a * b * c
end
local curriedMultiply = curry(multiply, 3)
print(curriedMultiply(2)(3)(4)) -- 24
Partial Application
Fix some arguments of a function, creating a new function:
local function partial(func, ...)
local fixedArgs = {...}
return function(...)
local allArgs = {}
for i, v in ipairs(fixedArgs) do
table.insert(allArgs, v)
end
for i, v in ipairs({...}) do
table.insert(allArgs, v)
end
return func(table.unpack(allArgs))
end
end
local function greet(greeting, name)
return greeting .. ", " .. name .. "!"
end
local sayHello = partial(greet, "Hello")
local sayHi = partial(greet, "Hi")
print(sayHello("Alice")) -- Hello, Alice!
print(sayHi("Bob")) -- Hi, Bob!
Click Run to execute your code
Function Decorators
Wrap functions to add behavior without modifying them:
-- Timing decorator
local function timed(func)
return function(...)
local start = os.clock()
local result = {func(...)}
local elapsed = os.clock() - start
print(string.format("Execution time: %.6f seconds", elapsed))
return table.unpack(result)
end
end
-- Logging decorator
local function logged(func, name)
return function(...)
print("Calling " .. name .. " with args:", ...)
local result = {func(...)}
print("Result:", table.unpack(result))
return table.unpack(result)
end
end
-- Memoization decorator
local function memoized(func)
local cache = {}
return function(x)
if cache[x] == nil then
cache[x] = func(x)
end
return cache[x]
end
end
-- Usage
local function fibonacci(n)
if n <= 1 then return n end
return fibonacci(n - 1) + fibonacci(n - 2)
end
local fastFib = memoized(fibonacci)
local timedFib = timed(fastFib)
print(timedFib(30))
Practical Examples
Data Processing Pipeline
local users = {
{name = "Alice", age = 25, active = true},
{name = "Bob", age = 30, active = false},
{name = "Charlie", age = 35, active = true},
{name = "David", age = 28, active = true}
}
-- Get names of active users over 25
local activeUsers = filter(users, function(u) return u.active end)
local over25 = filter(activeUsers, function(u) return u.age > 25 end)
local names = map(over25, function(u) return u.name end)
-- names = {"Charlie", "David"}
-- Or using composition
local getActiveUsersOver25 = pipe(
function(users) return filter(users, function(u) return u.active end) end,
function(users) return filter(users, function(u) return u.age > 25 end) end,
function(users) return map(users, function(u) return u.name end) end
)
local result = getActiveUsersOver25(users)
Validation Pipeline
local function validate(validators)
return function(value)
for i, validator in ipairs(validators) do
local valid, error = validator(value)
if not valid then
return false, error
end
end
return true
end
end
local function notEmpty(value)
if value == "" then
return false, "Value cannot be empty"
end
return true
end
local function minLength(min)
return function(value)
if #value < min then
return false, "Value must be at least " .. min .. " characters"
end
return true
end
end
local validateUsername = validate({
notEmpty,
minLength(3)
})
print(validateUsername("ab")) -- false, "Value must be at least 3 characters"
print(validateUsername("alice")) -- true
Click Run to execute your code
Practice Exercise
Try these advanced function challenges:
Click Run to execute your code
Summary
In this lesson, you learned:
- Higher-order functions that take or return functions
- Map, filter, and reduce patterns for data transformation
- Function composition for combining functions
- Currying to transform multi-argument functions
- Partial application for fixing function arguments
- Function decorators for adding behavior
- Practical examples: data pipelines and validation
What's Next?
Congratulations on completing Module 3! You've mastered functions, closures, and advanced functional programming techniques. Next, we'll dive into tables โLua's most versatile data structure. You'll learn how to use tables as arrays, dictionaries, objects, and more. Let's continue! ๐
Enjoying these tutorials?