A practical functional library for JavaScript programmers.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Ramda provides 55 powerful functions for function composition, currying, and higher-order operations. These functions enable the creation of elegant, reusable code through functional programming patterns.
Right-to-left function composition (mathematical style).
/**
* @param {...Function} fns - Functions to compose (right to left)
* @returns {Function} Composed function
*/
R.compose(...fns)
// Mathematical composition: (f ∘ g)(x) = f(g(x))
const transform = R.compose(
R.join('-'), // 4. Join with dashes
R.map(R.toLower), // 3. Convert to lowercase
R.split(' '), // 2. Split by spaces
R.trim // 1. Remove whitespace (executed first)
);
transform(' Hello World '); // => 'hello-world'
// Step by step execution:
// 1. R.trim(' Hello World ') => 'Hello World'
// 2. R.split(' ', 'Hello World') => ['Hello', 'World']
// 3. R.map(R.toLower, ['Hello', 'World']) => ['hello', 'world']
// 4. R.join('-', ['hello', 'world']) => 'hello-world'
// Mathematical operations
const calculate = R.compose(
Math.abs, // 3. Get absolute value
R.subtract(10), // 2. Subtract 10
R.multiply(2) // 1. Multiply by 2 (executed first)
);
calculate(3); // => 4
// Steps: 3 * 2 = 6, 6 - 10 = -4, abs(-4) = 4Left-to-right function composition (natural reading order).
/**
* @param {...Function} fns - Functions to compose (left to right)
* @returns {Function} Composed function
*/
R.pipe(...fns)
// Natural reading order: data flows left to right
const processUser = R.pipe(
R.prop('name'), // 1. Get name property
R.trim, // 2. Remove whitespace
R.toLower, // 3. Convert to lowercase
R.split(' '), // 4. Split into words
R.map(R.capitalize), // 5. Capitalize each word
R.join(' ') // 6. Join back together
);
const user = { name: ' john doe ' };
processUser(user); // => 'John Doe'
// Data processing pipeline
const analyzeNumbers = R.pipe(
R.filter(R.is(Number)), // 1. Keep only numbers
R.map(Math.abs), // 2. Get absolute values
R.sort(R.subtract), // 3. Sort ascending
R.takeLast(3), // 4. Take top 3
R.reduce(R.add, 0) // 5. Sum them
);
analyzeNumbers([1, -5, 'x', 3, -8, 2]); // => 10
// Steps: [1, -5, 3, -8, 2] → [1, 5, 3, 8, 2] → [1, 2, 3, 5, 8] → [3, 5, 8] → 16Binary function composition (curried compose for exactly 2 functions).
/**
* @param {Function} f - Second function to apply
* @param {Function} g - First function to apply
* @returns {Function} Composed function f(g(x))
*/
R.o(f, g)
const slugify = R.o(R.join('-'), R.split(' '));
slugify('Hello World'); // => 'Hello-World'
// More readable than nested calls
const getFirstWord = R.o(R.head, R.split(' '));
getFirstWord('Hello World'); // => 'Hello'
// Curried usage
const withStringLength = R.o(R.length);
const nameLength = withStringLength(R.prop('name'));
nameLength({ name: 'Alice' }); // => 5Left-to-right function composition with seed value.
/**
* @param {*} seed - Initial value to flow through functions
* @param {Array} pipeline - Array of functions to apply in sequence
* @returns {*} Final result after applying all functions
*/
R.flow(seed, pipeline)
// Process data through a pipeline
R.flow(9, [Math.sqrt, R.negate, R.inc]); // => -2
// Same as: R.inc(R.negate(Math.sqrt(9)))
// Transform and validate user input
const processInput = input => R.flow(input, [
R.trim,
R.toLower,
R.split(' '),
R.filter(R.complement(R.isEmpty)),
R.take(3)
]);
processInput(' Hello Beautiful World '); // => ['hello', 'beautiful', 'world']
// Data-first style (more intuitive than pipe sometimes)
R.flow([1, 2, 3, 4, 5], [
R.map(R.multiply(2)),
R.filter(R.gt(R.__, 5)),
R.reduce(R.add, 0)
]); // => 18Convert a regular function to a curried function.
/**
* @param {Function} fn - Function to curry
* @returns {Function} Curried version of the function
*/
R.curry(fn)
// Regular function
function add3(a, b, c) {
return a + b + c;
}
const curriedAdd = R.curry(add3);
// All these are equivalent:
curriedAdd(1, 2, 3); // => 6
curriedAdd(1)(2, 3); // => 6
curriedAdd(1, 2)(3); // => 6
curriedAdd(1)(2)(3); // => 6
// Partial application becomes natural
const add5 = curriedAdd(2)(3); // Waiting for one more argument
add5(4); // => 9
// Real-world example
const greet = R.curry((greeting, name, punctuation) =>
`${greeting}, ${name}${punctuation}`
);
const sayHello = greet('Hello');
const casualGreet = sayHello(R.__, '!');
casualGreet('Alice'); // => 'Hello, Alice!'Curry with explicit arity.
/**
* @param {Number} arity - Number of arguments to curry
* @param {Function} fn - Function to curry
* @returns {Function} Curried function with specified arity
*/
R.curryN(arity, fn)
// Useful for variadic functions
const sumAll = (...args) => R.sum(args);
const curriedSum = R.curryN(3, sumAll);
curriedSum(1)(2)(3); // => 6
curriedSum(1, 2)(3); // => 6
// Working with functions that have default parameters
const withDefaults = (a, b = 10, c = 20) => a + b + c;
const curriedWithDefaults = R.curryN(2, withDefaults);
curriedWithDefaults(5)(15); // => 40 (5 + 15 + 20)Partially apply arguments from left or right.
/**
* @param {Function} fn - Function to partially apply
* @param {Array} leftArgs - Arguments to apply from the left
* @returns {Function} Partially applied function
*/
R.partial(fn, leftArgs)
const multiply4 = (a, b, c, d) => a * b * c * d;
// Apply from left
const doubleAndSomething = R.partial(multiply4, [2, 3]);
doubleAndSomething(4, 5); // => 120 (2 * 3 * 4 * 5)
// Apply from right
const somethingAndTen = R.partialRight(multiply4, [2, 5]);
somethingAndTen(3, 4); // => 120 (3 * 4 * 2 * 5)
// Real-world example: pre-configured API calls
const apiCall = (method, url, headers, data) => ({ method, url, headers, data });
const postJSON = R.partial(apiCall, ['POST', '/api/users', {'Content-Type': 'application/json'}]);
postJSON({name: 'Alice'});
// => { method: 'POST', url: '/api/users', headers: {...}, data: {name: 'Alice'} }Reverse the order of the first two arguments.
/**
* @param {Function} fn - Function whose first two arguments to flip
* @returns {Function} Function with first two arguments reversed
*/
R.flip(fn)
const divide = (a, b) => a / b;
const flippedDivide = R.flip(divide);
divide(10, 2); // => 5
flippedDivide(10, 2); // => 0.2 (2 / 10)
// Useful with curried functions
const subtract = R.subtract; // (a, b) => a - b
const subtractFrom = R.flip(subtract); // (a, b) => b - a
const minus5 = subtract(R.__, 5); // x - 5
const subtract5From = subtractFrom(R.__, 5); // 5 - x
minus5(10); // => 5 (10 - 5)
subtract5From(10); // => -5 (5 - 10)R.__)The placeholder allows flexible partial application by marking positions for future arguments.
// Basic placeholder usage
const divide = R.divide; // (a, b) => a / b
const divideBy2 = divide(R.__, 2); // (x / 2)
const half = divideBy2(10); // => 5
const tenDividedBy = divide(10, R.__); // (10 / x)
const result = tenDividedBy(2); // => 5
// Multiple placeholders
const clamp = R.clamp; // (min, max, value) => clampedValue
const clampTo100 = clamp(0, 100, R.__);
clampTo100(150); // => 100
clampTo100(-10); // => 0
const clampFrom50 = clamp(50, R.__, R.__);
const clampFrom50To200 = clampFrom50(200);
clampFrom50To200(75); // => 75
// Complex example: building specialized filters
const filter = R.filter;
const propEq = R.propEq;
const activeUsers = filter(propEq('active', true), R.__);
const adminUsers = filter(propEq('role', 'admin'), R.__);
const users = [
{ name: 'Alice', active: true, role: 'admin' },
{ name: 'Bob', active: false, role: 'user' },
{ name: 'Carol', active: true, role: 'user' }
];
activeUsers(users); // => [Alice, Carol]
adminUsers(users); // => [Alice]Limit function arity.
/**
* @param {Function} fn - Function to limit
* @returns {Function} Function accepting only specified number of arguments
*/
R.unary(fn) // Accept only 1 argument
R.binary(fn) // Accept only 2 arguments
R.nAry(n, fn) // Accept only n arguments
const logArgs = (...args) => console.log('Arguments:', args);
const logOne = R.unary(logArgs);
const logTwo = R.binary(logArgs);
logArgs(1, 2, 3, 4); // Logs: Arguments: [1, 2, 3, 4]
logOne(1, 2, 3, 4); // Logs: Arguments: [1]
logTwo(1, 2, 3, 4); // Logs: Arguments: [1, 2]
// Useful with higher-order functions
['1', '2', '3'].map(parseInt); // => [1, NaN, NaN] (parseInt gets index as 2nd arg)
['1', '2', '3'].map(R.unary(parseInt)); // => [1, 2, 3]Ensure a function is called only once.
/**
* @param {Function} fn - Function to call only once
* @returns {Function} Function that can only be called once
*/
R.once(fn)
let counter = 0;
const increment = () => ++counter;
const incrementOnce = R.once(increment);
incrementOnce(); // => 1
incrementOnce(); // => 1 (same result, function not called again)
incrementOnce(); // => 1
// Real-world example: expensive initialization
const expensiveSetup = R.once(() => {
console.log('Performing expensive setup...');
return { initialized: true, timestamp: Date.now() };
});
const result1 = expensiveSetup(); // Logs message, returns object
const result2 = expensiveSetup(); // No log, returns same object
console.log(result1 === result2); // => trueCache function results based on arguments.
/**
* @param {Function} keyGen - Function to generate cache key from arguments
* @param {Function} fn - Function to memoize
* @returns {Function} Memoized function
*/
R.memoizeWith(keyGen, fn)
// Expensive fibonacci calculation
const fib = R.memoizeWith(R.identity, n => {
console.log(`Calculating fib(${n})`);
return n < 2 ? n : fib(n - 1) + fib(n - 2);
});
fib(10); // Calculates and caches intermediate results
fib(8); // Returns cached result immediately
// Custom cache key generation
const expensiveUserLookup = R.memoizeWith(
user => `${user.id}-${user.version}`,
user => {
console.log(`Looking up user ${user.id}`);
// Expensive database/API call
return { ...user, enrichedData: 'expensive-computation' };
}
);Apply function to argument array.
/**
* @param {Function} fn - Function to apply
* @param {Array} args - Array of arguments
* @returns {*} Result of applying fn to args
*/
R.apply(fn, args)
const nums = [1, 2, 3, 4, 5];
R.apply(Math.max, nums); // => 5 (same as Math.max(...nums))
R.apply(R.add, [10, 20]); // => 30
R.apply(R.concat, [['a'], ['b']]); // => ['a', 'b']
// Useful in function compositions
const getMaxScore = R.pipe(
R.pluck('score'), // Extract scores: [85, 92, 78]
R.apply(Math.max) // Find maximum: 92
);
const students = [
{ name: 'Alice', score: 85 },
{ name: 'Bob', score: 92 },
{ name: 'Carol', score: 78 }
];
getMaxScore(students); // => 92Call function with provided arguments.
/**
* @param {Function} fn - Function to call
* @param {...*} args - Arguments to pass to fn
* @returns {*} Result of calling fn with args
*/
R.call(fn, ...args)
R.call(R.add, 1, 2); // => 3
R.call(Math.max, 5, 10, 3); // => 10
// Useful in converge patterns
const average = R.converge(R.divide, [R.sum, R.length]);
const weightedAverage = R.converge(
R.call,
[
R.pipe(R.pluck('weight'), R.reduce(R.multiply, 1)),
R.pluck('value')
]
);Apply multiple functions to same arguments.
/**
* @param {Array<Function>} fns - Array of functions to apply
* @returns {Function} Function that returns array of results
*/
R.juxt(fns)
const getStats = R.juxt([
R.length,
R.sum,
R.mean,
Math.min,
Math.max
]);
getStats([1, 2, 3, 4, 5]); // => [5, 15, 3, 1, 5]
// Real-world example: form validation
const validateUser = R.juxt([
R.pipe(R.prop('email'), R.test(/@/)), // Has @ in email
R.pipe(R.prop('age'), R.gte(R.__, 18)), // At least 18
R.pipe(R.prop('name'), R.complement(R.isEmpty)) // Name not empty
]);
const user = { email: 'alice@example.com', age: 25, name: 'Alice' };
validateUser(user); // => [true, true, true]Apply multiple functions to same input, then combine results.
/**
* @param {Function} converging - Function to combine results
* @param {Array<Function>} branching - Functions to apply to input
* @returns {Function} Converged function
*/
R.converge(converging, branching)
// Calculate average: sum / length
const average = R.converge(R.divide, [R.sum, R.length]);
average([1, 2, 3, 4, 5]); // => 3
// Create full name from object
const fullName = R.converge(R.concat, [
R.prop('firstName'),
R.pipe(R.prop('lastName'), R.concat(' '))
]);
fullName({ firstName: 'John', lastName: 'Doe' }); // => 'John Doe'
// Complex example: calculate compound interest
const compoundInterest = R.converge(
(principal, rate, time, compound) =>
principal * Math.pow(1 + rate / compound, compound * time),
[
R.prop('principal'),
R.prop('rate'),
R.prop('time'),
R.prop('compoundFreq')
]
);
const investment = {
principal: 1000,
rate: 0.05,
time: 10,
compoundFreq: 4
};
compoundInterest(investment); // => 1643.62Lift functions to work with wrapped values (applicative functors).
/**
* @param {Function} fn - Function to lift
* @returns {Function} Lifted function that works with arrays/wrapped values
*/
R.lift(fn)
// Lift binary function to work with arrays
const liftedAdd = R.lift(R.add);
liftedAdd([1, 2], [10, 20]); // => [11, 21, 12, 22]
// Lift ternary function
const liftedAdd3 = R.liftN(3, (a, b, c) => a + b + c);
liftedAdd3([1, 2], [10], [100, 200]); // => [111, 211, 112, 212]
// Real-world: generate test combinations
const createUser = R.liftN(3, (name, age, role) => ({ name, age, role }));
const testUsers = createUser(
['Alice', 'Bob'],
[25, 30],
['admin', 'user']
);
// => 8 user objects with all combinationsApply wrapped functions to wrapped values.
/**
* @param {Array<Function>} wrappedFns - Array of functions
* @param {Array} wrappedVals - Array of values
* @returns {Array} Results of applying each function to each value
*/
R.ap(wrappedFns, wrappedVals)
const functions = [R.multiply(2), R.add(3)];
const values = [1, 2, 3];
R.ap(functions, values); // => [2, 4, 6, 4, 5, 6]
// S combinator behavior with two functions
R.ap(R.concat, R.toUpper)('ramda'); // => 'ramdaRAMDA'Execute side effect function then return original value.
/**
* @param {Function} fn - Side effect function
* @param {*} x - Value to pass through
* @returns {*} Original value unchanged
*/
R.tap(fn, x)
// Debug pipeline values
const debugPipeline = R.pipe(
R.map(R.multiply(2)),
R.tap(console.log), // Logs: [2, 4, 6, 8]
R.filter(R.gt(R.__, 5)),
R.tap(console.log), // Logs: [6, 8]
R.reduce(R.add, 0)
);
debugPipeline([1, 2, 3, 4]); // => 14
// Functional logging
const logAndReturn = R.tap(x => console.log('Processing:', x));
logAndReturn(42); // Logs "Processing: 42", returns 42
// Side effects in chains
const processUser = R.pipe(
R.tap(user => analytics.track('user_processed', user)),
R.assoc('processed', true),
R.tap(user => cache.set(user.id, user))
);Functional error handling with try/catch logic.
/**
* @param {Function} tryer - Function to attempt
* @param {Function} catcher - Function to handle errors
* @returns {Function} Function that returns tryer result or catcher result on error
*/
R.tryCatch(tryer, catcher)
// Safe JSON parsing
const safeParseJSON = R.tryCatch(JSON.parse, R.always({}));
safeParseJSON('{"valid": true}'); // => {valid: true}
safeParseJSON('invalid json'); // => {}
// Safe property access
const safeProp = key => R.tryCatch(R.prop(key), R.always(null));
const getName = safeProp('name');
getName({name: 'John'}); // => 'John'
getName(null); // => null (no error)
// API error handling
const fetchUserData = id => R.tryCatch(
() => api.getUser(id),
(error) => ({ error: error.message, id })
);
// Compose with other functions
const processData = R.pipe(
R.tryCatch(JSON.parse, R.always({})),
R.prop('data'),
R.defaultTo([])
);Add index parameter to iteration functions.
/**
* @param {Function} fn - Iteration function (like map, filter)
* @returns {Function} New function that passes index as second parameter
*/
R.addIndex(fn)
// Map with index
const mapIndexed = R.addIndex(R.map);
mapIndexed((val, idx) => `${idx}: ${val}`, ['a', 'b', 'c']);
// => ['0: a', '1: b', '2: c']
// Filter with index
const filterIndexed = R.addIndex(R.filter);
filterIndexed((val, idx) => idx % 2 === 0, ['a', 'b', 'c', 'd']);
// => ['a', 'c'] (even indices)
// Real-world: create numbered lists
const createNumberedList = R.addIndex(R.map)((item, index) =>
`${index + 1}. ${item}`
);
createNumberedList(['Buy milk', 'Walk dog', 'Code']);
// => ['1. Buy milk', '2. Walk dog', '3. Code']
// Combine with other operations
const processWithIndex = R.pipe(
R.addIndex(R.map)((item, idx) => ({ ...item, position: idx })),
R.filter(R.propEq('active', true))
);These function utilities enable powerful abstractions and elegant solutions to complex programming problems through composition, partial application, and higher-order transformations.
Install with Tessl CLI
npx tessl i tessl/npm-ramda