Skip to content
AgileLogics
4 min read

Functional Style

Prerequisites: Closures, Advanced tables

Lua supports a functional style well: functions are values, closures are cheap, and tail calls don't grow the stack.

Composition and partial application

local function compose(...)
  local fns = table.pack(...)
  return function(...)
    local results = table.pack(...)
    for i = fns.n, 1, -1 do
      results = table.pack(fns[i](table.unpack(results, 1, results.n)))
    end
    return table.unpack(results, 1, results.n)
  end
end

local function partial(fn, a)
  return function(...) return fn(a, ...) end
end

local trim = function(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end
local slug = compose(function(s) return (s:gsub("%W+", "-")) end, string.lower, trim)
print(slug("  Hello Lua World  "))

local add = function(a, b) return a + b end
local add10 = partial(add, 10)
print(add10(5))
hello-lua-world
15

Proper tail calls

A call in the form return f(...) is a tail call: Lua reuses the current stack frame, so tail recursion never overflows:

local function countdown(n)
  if n == 0 then return "liftoff" end
  return countdown(n - 1)
end
print(countdown(1000000))

local function sum(n, acc)
  acc = acc or 0
  if n == 0 then return acc end
  return sum(n - 1, acc + n)
end
print(sum(1000000))
liftoff
500000500000

Only return f(args) exactly is a tail call — return f(args) + 1 or return (f(args)) isn't.

Memoization

local function memoize(fn)
  local cache = {}
  return function(n)
    if cache[n] == nil then cache[n] = fn(n) end
    return cache[n]
  end
end

local fib
fib = memoize(function(n)
  if n < 2 then return n end
  return fib(n - 1) + fib(n - 2)
end)
print(fib(80))
23416728348467685

Without the cache, fib(80) would take longer than the age of the universe; with it, it's instant.

What's next

Try the Intermediate Projects, then continue to Level 3 — Advanced: environments, performance, embedding, and Lua in games, editors, and servers.