A functional programming library for Lua inspired by Haskell and Elixir, designed to make data transformations concise and composable.
fp-lua provides a comprehensive set of functional utilities with automatic currying, enabling you to write clean, declarative code without multiple loops or intermediate variables.
Before:
local active_emails = {}
for _, user in ipairs(users) do
if user.active then
if user.email then
table.insert(active_emails, user.email)
end
end
endAfter:
local active_emails = pipe(
filter(prop("active")),
map(prop("email")),
compact
)(users)- Auto-curried functions - All core functions support partial application
- Composable pipelines - Build complex transformations from simple pieces
- Immutable operations - Work with data without mutating original structures
- Rich standard library - Map, filter, fold, scan, chunk, partition, and more
- Zero dependencies - Pure Lua implementation
Clone this repository and require the module:
local FP = require("fp-lua")local FP = require("fp-lua")
-- Import functions you'll use
local map, filter, pipe = FP.map, FP.filter, FP.pipe
local prop, gt = FP.prop, FP.gt
-- Transform data
local users = {
{name = "Alice", age = 30, active = true},
{name = "Bob", age = 25, active = false},
{name = "Charlie", age = 35, active = true}
}
-- Get names of active users over 25
local result = pipe(
filter(prop("active")),
filter(function(u) return u.age > 25 end),
map(prop("name"))
)(users)
-- Result: {"Alice", "Charlie"}All functions are automatically curried, meaning you can call them with fewer arguments and get back a function waiting for the rest:
-- These are equivalent:
map(double, numbers)
map(double)(numbers)
-- Partial application
local get_names = map(prop("name"))
get_names(users)
get_names(customers)Build complex operations from simple functions:
-- compose: right-to-left (mathematical style)
local process = compose(
sum,
map(double),
filter(is_positive)
)
-- pipe: left-to-right (data flow style)
local process = pipe(
filter(is_positive),
map(double),
sum
)Transform each element in a list.
map(function(x) return x * 2 end, {1, 2, 3})
-- Result: {2, 4, 6}Keep elements that satisfy the predicate.
filter(function(x) return x > 2 end, {1, 2, 3, 4})
-- Result: {3, 4}Map then flatten one level.
flat_map(function(x) return {x, x * 2} end, {1, 2})
-- Result: {1, 2, 2, 4}Reduce from left with an accumulator.
foldl(function(acc, x) return acc + x end, 0, {1, 2, 3})
-- Result: 6Reduce from right with an accumulator.
foldr(function(x, acc) return acc .. x end, "", {"a", "b", "c"})
-- Result: "cba"Like foldl but returns all intermediate results.
scanl(function(acc, x) return acc + x end, 0, {1, 2, 3})
-- Result: {0, 1, 3, 6}Like foldr but returns all intermediate results.
scanr(function(x, acc) return acc + x end, 0, {1, 2, 3})
-- Result: {6, 5, 3, 0}Take first n elements.
take(2, {1, 2, 3, 4})
-- Result: {1, 2}Drop first n elements.
drop(2, {1, 2, 3, 4})
-- Result: {3, 4}Take elements while predicate is true.
take_while(function(x) return x < 3 end, {1, 2, 3, 4})
-- Result: {1, 2}Drop elements while predicate is true.
drop_while(function(x) return x < 3 end, {1, 2, 3, 4})
-- Result: {3, 4}Split into chunks of specified size.
chunk(2, {1, 2, 3, 4, 5})
-- Result: {{1, 2}, {3, 4}, {5}}Split into chunks when fn returns a new value.
chunk_by(function(x) return x % 2 end, {1, 3, 2, 4, 5})
-- Result: {{1, 3}, {2, 4}, {5}}Create sliding windows of specified size.
window(2, {1, 2, 3, 4})
-- Result: {{1, 2}, {2, 3}, {3, 4}}Split list at elements matching predicate.
split(function(x) return x == 0 end, {1, 2, 0, 3, 4})
-- Result: {{1, 2}, {3, 4}}Split into two lists: matching and non-matching.
partition(function(x) return x % 2 == 0 end, {1, 2, 3, 4})
-- Result: {{2, 4}, {1, 3}}Check if all elements satisfy predicate.
all(function(x) return x > 0 end, {1, 2, 3})
-- Result: trueCheck if any element satisfies predicate.
any(function(x) return x > 5 end, {1, 2, 3})
-- Result: falseCompose functions right-to-left.
local f = compose(add_one, double, square)
f(3)
-- Evaluates: add_one(double(square(3)))
-- = add_one(double(9))
-- = add_one(18)
-- = 19Compose functions left-to-right.
local f = pipe(square, double, add_one)
f(3)
-- Evaluates: add_one(double(square(3)))
-- = 19Reverse the order of a function's first two arguments.
local subtract = function(a, b) return a - b end
local flipped = flip(subtract)
flipped(3, 10)
-- Result: 7 (computes 10 - 3)Return the argument unchanged.
identity(5)
-- Result: 5Return a function that always returns x.
local always_five = const(5)
always_five(1, 2, 3)
-- Result: 5Make a function auto-curried.
local add = curry(function(a, b, c)
return a + b + c
end, 3)
add(1)(2)(3) -- 6
add(1, 2)(3) -- 6
add(1, 2, 3) -- 6Partially apply arguments to a function.
local add = function(a, b, c)
return a + b + c
end
local add5 = partial(add, 5)
add5(2, 3)
-- Result: 10Return new table with key set to value (immutable).
assoc("age", 30, {name = "Alice"})
-- Result: {name = "Alice", age = 30}Merge two tables (immutable).
merge({a = 1}, {b = 2})
-- Result: {a = 1, b = 2}Select only specified keys from table.
pick({"name", "age"}, {name = "Alice", age = 30, city = "NYC"})
-- Result: {name = "Alice", age = 30}Exclude specified keys from table.
omit({"password"}, {name = "Alice", password = "secret"})
-- Result: {name = "Alice"}Get nested value by path.
get_in({"user", "address", "city"}, data)Update nested value by path (immutable).
update_in(
{"user", "age"},
function(age) return age + 1 end,
data
)Extract a property from a table.
map(prop("name"), users)
-- Extracts all names from usersExtract a property from each table in a list.
pluck("name", users)
-- Same as: map(prop("name"), users)Remove nil values.
compact({1, nil, 2, nil, 3})
-- Result: {1, 2, 3}Remove duplicates.
uniq({1, 2, 2, 3, 1})
-- Result: {1, 2, 3}Count elements or elements matching predicate.
count({1, 2, 3})
-- Result: 3
count(function(x) return x > 2 end, {1, 2, 3, 4})
-- Result: 2Concatenate two lists.
append({1, 2}, {3, 4})
-- Result: {1, 2, 3, 4}eq(5) -- function(x) return x == 5 end
gt(5) -- function(x) return x > 5 end
lt(5) -- function(x) return x < 5 end
gte(5) -- function(x) return x >= 5 end
lte(5) -- function(x) return x <= 5 end
not_(fn) -- function(...) return not fn(...) end
both(f, g) -- function(x) return f(x) and g(x) end
either(f, g) -- function(x) return f(x) or g(x) endMIT License - see LICENSE file for details
- Currying
- Table Operations
- Comparison Helpers
- List Transformations
- Folds and Scans
- Slicing
- Chunking and Windowing
- Predicates
- Combinators
- Utilities
Made with ❤️ for the Lua community