CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ramda

A practical functional library for JavaScript programmers.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

function-functions.mddocs/

Function Functions

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.

Function Composition

compose

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) = 4

pipe

Left-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] → 16

o

Binary 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' }); // => 5

flow

Left-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)
]); // => 18

Currying and Partial Application

curry

Convert 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!'

curryN

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)

partial, partialRight

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'} }

flip

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)

The Placeholder (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]

Function Transformation

unary, binary, nAry

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]

once

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); // => true

memoizeWith

Cache 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' };
  }
);

Function Analysis and Inspection

apply

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); // => 92

call

Call 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')
  ]
);

juxt

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]

converge

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.62

Higher-Order Function Utilities

lift, liftN

Lift 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 combinations

ap

Apply 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'

Debugging and Error Handling

tap

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))
);

tryCatch

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([])
);

addIndex

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

docs

function-functions.md

index.md

list-functions.md

math-logic.md

object-functions.md

string-type.md

tile.json