Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Lua Dictionary
  3. Closures and Higher-order Functions

Closures and Higher-order Functions

In Lua, functions are first-class values and can be assigned to variables or treated as arguments and return values of other functions. A closure is a function that retains variables from an outer scope (upvalues), enabling flexible design when combined with higher-order functions.

Syntax

-- -----------------------------------------------
-- Basic closure syntax
-- -----------------------------------------------
-- Captures a variable from the outer scope (upvalue)
local function outer()
    local upvalue = 0          -- upvalue captured by the closure
    return function()          -- returns an anonymous function as a closure
        upvalue = upvalue + 1
        return upvalue
    end
end

local counter = outer()        -- obtain the closure as a counter
print(counter())               -- 1
print(counter())               -- 2

-- -----------------------------------------------
-- Basic higher-order function syntax (takes a function as argument)
-- -----------------------------------------------
-- Receives fn and applies it to each element of the list, returning a new table
local function map(tbl, fn)
    local result = {}
    for i, v in ipairs(tbl) do
        result[i] = fn(v)      -- passes each element to fn for transformation
    end
    return result
end

-- -----------------------------------------------
-- Function that returns a function (function factory)
-- -----------------------------------------------
-- Creates and returns a closure that adds n
local function make_adder(n)
    return function(x)
        return x + n           -- n is retained as an upvalue
    end
end

local add10 = make_adder(10)
print(add10(5))                -- 15

-- -----------------------------------------------
-- Memoization
-- -----------------------------------------------
-- Creates a closure that caches computation results
local function memoize(fn)
    local cache = {}           -- the closure holds the cache table as an upvalue
    return function(n)
        if cache[n] == nil then
            cache[n] = fn(n)
        end
        return cache[n]
    end
end

Syntax Reference

Concept / PatternDescription
UpvalueA variable captured by a closure from an outer scope. The upvalue is retained as long as the closure remains alive.
ClosureA function that has upvalues. In Lua, all functions can potentially be closures.
Counter generationPattern that returns a counter function holding state via an upvalue. The internal state is updated on each call.
Function factoryA function that creates and returns closures with different behavior based on arguments. make_adder and make_multiplier are typical examples.
mapA higher-order function that applies a function to each element of a table and returns a new table. Does not modify the original table.
filterA higher-order function that applies a predicate to each element and returns a new table containing only elements for which the predicate returns true.
reduceA higher-order function that accumulates each element of a table into a single value. Used for aggregations such as sum and maximum.
MemoizationAn optimization pattern that saves computation results in a cache table inside a closure, avoiding redundant computation for the same arguments.
Partial applicationPattern that returns a new function with some arguments of the original function fixed. An upvalue holds the fixed values.

Sample Code

eva_closure.lua
-- eva_closure.lua — closures and higher-order functions sample
-- Uses Evangelion character data to demonstrate
-- closures, map, filter, reduce, and memoization

-- -----------------------------------------------
-- Counter generation (holding state with an upvalue)
-- -----------------------------------------------

-- Creates a counter to manage pilot IDs
local function make_counter(start, label)
    local count = start        -- state is held as an upvalue
    return function()
        local current = count
        count = count + 1
        return label .. current
    end
end

local next_pilot_id = make_counter(1, "PILOT-")
print("=== Pilot ID Assignment ===")
print(next_pilot_id())         -- PILOT-1
print(next_pilot_id())         -- PILOT-2
print(next_pilot_id())         -- PILOT-3
print("")

-- -----------------------------------------------
-- Higher-order functions: implementing map / filter / reduce
-- -----------------------------------------------

-- map: applies fn to each element and returns a new table
local function map(tbl, fn)
    local result = {}
    for i, v in ipairs(tbl) do
        result[i] = fn(v)
    end
    return result
end

-- filter: returns a new table with only elements for which fn returns true
local function filter(tbl, fn)
    local result = {}
    for _, v in ipairs(tbl) do
        if fn(v) then
            result[#result + 1] = v
        end
    end
    return result
end

-- reduce: folds tbl starting from init using fn
local function reduce(tbl, fn, init)
    local acc = init
    for _, v in ipairs(tbl) do
        acc = fn(acc, v)
    end
    return acc
end

-- Pilot sync rate data
local pilots = {
    { name = "Ikari Shinji",         sync = 400, unit = "EVA-01" },
    { name = "Ayanami Rei",          sync = 380, unit = "EVA-00" },
    { name = "Soryu Asuka Langley",  sync = 300, unit = "EVA-02" },
    { name = "Nagisa Kaworu",        sync = 999, unit = "EVA-13" },
    { name = "Katsuragi Misato",     sync = 0,   unit = "none"   },
}

-- map: generate a list of names only
local names = map(pilots, function(p) return p.name end)
print("=== Pilot List ===")
for _, name in ipairs(names) do
    print("  " .. name)
end
print("")

-- filter: narrow down to pilots with sync rate >= 300
local active = filter(pilots, function(p) return p.sync >= 300 end)
print("=== Sync Rate >= 300 ===")
for _, p in ipairs(active) do
    print(string.format("  %s (%s) Sync rate: %d", p.name, p.unit, p.sync))
end
print("")

-- reduce: calculate the total sync rate
local total = reduce(pilots, function(acc, p) return acc + p.sync end, 0)
print("=== Total Sync Rate ===")
print("  Total: " .. total)
print("")

-- -----------------------------------------------
-- Function factory (function that returns a closure)
-- -----------------------------------------------

-- Creates a closure that multiplies sync rate by a factor
local function make_sync_booster(multiplier)
    return function(p)
        return {
            name = p.name,
            unit = p.unit,
            sync = math.floor(p.sync * multiplier),
        }
    end
end

local boost_x1_5 = make_sync_booster(1.5)   -- 1.5x booster
local boosted = map(active, boost_x1_5)

print("=== After 1.5x Sync Rate Boost ===")
for _, p in ipairs(boosted) do
    print(string.format("  %s: %d", p.name, p.sync))
end
print("")

-- -----------------------------------------------
-- Memoization (optimizing recursive functions)
-- -----------------------------------------------

-- Creates a memoization wrapper
local function memoize(fn)
    local cache = {}           -- the closure holds the cache as an upvalue
    return function(n)
        if cache[n] == nil then
            cache[n] = fn(n)
        end
        return cache[n]
    end
end

-- Memoize a naive recursive Fibonacci function
local fib
fib = memoize(function(n)
    if n <= 1 then return n end
    return fib(n - 1) + fib(n - 2)   -- fib is memoized so this is fast
end)

print("=== Fibonacci (searching up to Nagisa Kaworu's sync rate of 999) ===")
-- Find the largest Fibonacci number <= 999
local best = 0
for i = 0, 16 do
    local f = fib(i)
    if f <= 999 then best = f end
end
print("  Largest Fibonacci number <= 999: " .. best)
print("")

-- -----------------------------------------------
-- Partial application
-- -----------------------------------------------

-- Returns a closure with the first argument of fn fixed to a
local function partial(fn, a)
    return function(b)
        return fn(a, b)
    end
end

-- Calculates sync score (base + bonus)
local function calc_score(base, bonus)
    return base + bonus
end

local shinji_score = partial(calc_score, 400)   -- fix Ikari Shinji's base sync rate
print("=== Partial Application: Ikari Shinji Score Calculation ===")
print("  Bonus  50: " .. shinji_score(50))   -- 450
print("  Bonus 100: " .. shinji_score(100))  -- 500
print("  Bonus 200: " .. shinji_score(200))  -- 600
lua eva_closure.lua
=== Pilot ID Assignment ===
PILOT-1
PILOT-2
PILOT-3

=== Pilot List ===
  Ikari Shinji
  Ayanami Rei
  Soryu Asuka Langley
  Nagisa Kaworu
  Katsuragi Misato

=== Sync Rate >= 300 ===
  Ikari Shinji (EVA-01) Sync rate: 400
  Ayanami Rei (EVA-00) Sync rate: 380
  Soryu Asuka Langley (EVA-02) Sync rate: 300
  Nagisa Kaworu (EVA-13) Sync rate: 999

=== Total Sync Rate ===
  Total: 2079

=== After 1.5x Sync Rate Boost ===
  Ikari Shinji: 600
  Ayanami Rei: 570
  Soryu Asuka Langley: 450
  Nagisa Kaworu: 1498

=== Fibonacci (searching up to Nagisa Kaworu's sync rate of 999) ===
  Largest Fibonacci number <= 999: 987

=== Partial Application: Ikari Shinji Score Calculation ===
  Bonus  50: 450
  Bonus 100: 500
  Bonus 200: 600

Common Mistakes

Common Mistake 1: Closures inside loops sharing the loop variable

In Lua, for loop variables have an independent local scope for each iteration, so the classic pitfall (all closures capturing only the final value) does not occur. However, caution is needed when using while loops or manual counters.

ng_example.lua
local funcs = {}
local i = 1
while i <= 3 do
    funcs[i] = function() return i end  -- captures i directly
    i = i + 1
end

for _, f in ipairs(funcs) do
    print(f())  -- all print 4 (the value of i after the loop)
end
4
4
4

Copy the loop variable's value into a local variable and capture that instead.

ok_example.lua
local funcs = {}
local i = 1
while i <= 3 do
    local captured = i  -- copy the current value of i
    funcs[i] = function() return captured end
    i = i + 1
end

for _, f in ipairs(funcs) do
    print(f())
end
1
2
3

Common Mistake 2: Not being aware that multiple closures share the same upvalue

Multiple closures that reference the same outer-scope variable share that variable. When one closure updates the variable, the others are affected too.

ng_example2.lua
local function make_pair()
    local count = 0
    local inc = function() count = count + 1 end
    local get = function() return count end
    return inc, get
end

local inc, get = make_pair()
print(get())  -- 0
inc()
inc()
print(get())  -- 2 -- inc and get share count
0
2

This may be intentional, but to prevent unintended sharing, give each closure its own independent variable.

ok_example2.lua
local function make_counter()
    local count = 0
    return {
        inc   = function() count = count + 1 end,
        get   = function() return count end,
        reset = function() count = 0 end,
    }
end

local c = make_counter()
c.inc()
c.inc()
print(c.get())   -- 2
c.reset()
print(c.get())   -- 0
2
0

Overview

In Lua, functions are first-class values and can be freely assigned to variables, passed as arguments, and used as return values. A closure is a function that captures variables from an outer scope (upvalues). Because upvalues are not garbage-collected as long as the closure remains alive, they are useful for implementing stateful counters and caches.

Higher-order functions are functions that take a function as an argument or return a function. Implementing map, filter, and reduce yourself allows table operations to be written declaratively. Lua's standard library does not include these, but they can be implemented in a few dozen lines.

Function factories generate closures with different behavior based on arguments, as in make_adder or make_sync_booster. Memoization caches computation results in a table inside a closure, significantly reducing redundant computation in recursive cases like Fibonacci. Partial application generates a specialized function from a general one by holding fixed arguments as upvalues. For table fundamentals, see Table Basics; for basic function definitions, see Function Basics.

If you find any errors or copyright issues, please .