Web Analytics

Advanced Functions in Lua

Intermediate ~35 min read

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
Output
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
Output
Click Run to execute your code
Best Practice: Map, filter, and reduce make your code more declarative and easier to understand. Instead of writing loops, you describe what you want to do, not how to do it.

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
Output
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!
Output
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
Output
Click Run to execute your code

Practice Exercise

Try these advanced function challenges:

Output
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! ๐Ÿš€