Getting started
Installation
Ramda is a practical functional library for JavaScript that emphasizes a purer functional style with automatic currying and immutability.
npm install ramda
npm install --save-dev @types/ramda
Import patterns
import * as R from "ramda"; // Full import (v0.25+)
import { map, filter } from "ramda"; // Named imports (tree-shaking)
const R = require("ramda"); // CommonJS
Important: No default export in v0.25+. Use import * as R not import R.
Core concepts
| Concept | Description |
|---|---|
| Automatic currying | Every function is curried by default |
| Data-last | Data is always the last argument |
| Immutability | Never mutates input data |
| Point-free | Compose without explicit arguments |
// Currying
const add2 = R.add(2);
add2(3); //=> 5
// Data-last
const filterLarge = R.filter((x) => x > 10);
filterLarge([5, 15, 25]); //=> [15, 25]
// Immutability
const original = [1, 2, 3];
const updated = R.append(4, original);
// original unchanged: [1, 2, 3]
Placeholder
// Skip arguments with R.__
const divide10By = R.divide(10, R.__);
divide10By(2); //=> 5
const greet = R.replace("{name}", R.__);
greet("Hello, {name}!")("Alice"); //=> 'Hello, Alice!'
Composition
R.compose (right-to-left)
const scream = R.compose(R.toUpper, R.concat("Hello, "));
scream("world"); //=> 'HELLO, WORLD'
// Execution order: concat → toUpper
R.pipe (left-to-right)
const getAge = R.pipe(R.prop("birthYear"), (year) => 2026 - year);
getAge({ birthYear: 1990 }); //=> 36
// Execution order: prop → calculation
Debugging with R.tap
const debugPipe = R.pipe(
R.assoc("a", 2),
R.tap(console.log), // logs intermediate value
R.assoc("a", 3),
);
debugPipe({ a: 1 });
// logs: {a: 2}
// returns: {a: 3}
Composition helpers
R.identity(42); //=> 42
R.always("Tee")(); //=> 'Tee'
R.T(); //=> true
R.F(); //=> false
List operations
Transform
R.map((x) => x * 2, [1, 2, 3]);
//=> [2, 4, 6]
R.filter((x) => x > 2, [1, 2, 3, 4]);
//=> [3, 4]
R.reject((x) => x > 2, [1, 2, 3, 4]);
//=> [1, 2]
R.reduce(R.add, 0, [1, 2, 3]);
//=> 6
R.find((x) => x > 2, [1, 2, 3]);
//=> 3
R.findIndex((x) => x > 2, [1, 2, 3]);
//=> 2
R.pluck("name", [{ name: "A" }, { name: "B" }]);
//=> ['A', 'B']
R.chain((n) => [n, n], [1, 2, 3]);
//=> [1, 1, 2, 2, 3, 3]
Access elements
R.head([1, 2, 3]); //=> 1
R.tail([1, 2, 3]); //=> [2, 3]
R.last([1, 2, 3]); //=> 3
R.init([1, 2, 3]); //=> [1, 2]
R.nth(1, [1, 2, 3]); //=> 2
R.nth(-1, [1, 2, 3]); //=> 3
Slice operations
R.take(2, [1, 2, 3, 4]);
//=> [1, 2]
R.drop(2, [1, 2, 3, 4]);
//=> [3, 4]
R.takeLast(2, [1, 2, 3, 4]);
//=> [3, 4]
R.dropLast(2, [1, 2, 3, 4]);
//=> [1, 2]
R.slice(1, 3, [1, 2, 3, 4]);
//=> [2, 3]
Flatten and unique
R.flatten([1, [2, [3]]]);
//=> [1, 2, 3]
R.unnest([1, [2, [3]]]);
//=> [1, 2, [3]]
R.uniq([1, 2, 1, 3]);
//=> [1, 2, 3]
R.uniqBy(Math.abs, [-1, 1, 2]);
//=> [-1, 2]
Sort operations
R.sort((a, b) => a - b, [3, 1, 2]);
//=> [1, 2, 3]
R.sortBy(R.prop("age"), users);
R.sortWith([R.ascend(R.prop("age")), R.descend(R.prop("name"))], users);
Group and partition
R.groupBy((x) => (x > 10 ? "big" : "small"), [5, 15, 3, 25]);
//=> {small: [5, 3], big: [15, 25]}
R.partition((x) => x > 10, [5, 15, 3, 25]);
//=> [[15, 25], [5, 3]]
Combine arrays
R.zip([1, 2], ["a", "b"]);
//=> [[1,'a'], [2,'b']]
R.zipWith(R.add, [1, 2], [3, 4]);
//=> [4, 6]
R.zipObj(["a", "b"], [1, 2]);
//=> {a: 1, b: 2}
R.concat([1, 2], [3, 4]);
//=> [1, 2, 3, 4]
Modify arrays
R.append(4, [1, 2, 3]);
//=> [1, 2, 3, 4]
R.prepend(0, [1, 2, 3]);
//=> [0, 1, 2, 3]
R.insert(2, "x", ["a", "b", "c"]);
//=> ['a', 'b', 'x', 'c']
R.adjust(1, R.toUpper, ["a", "b", "c"]);
//=> ['a', 'B', 'c']
R.update(1, "x", ["a", "b", "c"]);
//=> ['a', 'x', 'c']
R.remove(1, 2, ["a", "b", "c", "d"]);
//=> ['a', 'd']
Generate arrays
R.range(1, 5); //=> [1, 2, 3, 4]
R.repeat("a", 3); //=> ['a', 'a', 'a']
R.times(R.identity, 5); //=> [0, 1, 2, 3, 4]
Object operations
Access properties
R.prop("x", { x: 100 });
//=> 100
R.path(["a", "b"], { a: { b: 2 } });
//=> 2
R.propOr("N/A", "x", {});
//=> 'N/A'
R.pathOr("N/A", ["a", "b"], {});
//=> 'N/A'
R.props(["x", "y"], { x: 1, y: 2 });
//=> [1, 2]
Select and omit
R.pick(["a", "c"], { a: 1, b: 2, c: 3 });
//=> {a: 1, c: 3}
R.omit(["a"], { a: 1, b: 2, c: 3 });
//=> {b: 2, c: 3}
R.pickBy((v, k) => v > 1, { a: 1, b: 2, c: 3 });
//=> {b: 2, c: 3}
Set and remove
R.assoc("c", 3, { a: 1, b: 2 });
//=> {a: 1, b: 2, c: 3}
R.assocPath(["a", "b"], 42, { a: {} });
//=> {a: {b: 42}}
R.dissoc("b", { a: 1, b: 2 });
//=> {a: 1}
R.dissocPath(["a", "b"], { a: { b: 1, c: 2 } });
//=> {a: {c: 2}}
Merge objects
R.mergeLeft({ a: 1 }, { a: 2, b: 3 });
//=> {a: 1, b: 3}
R.mergeRight({ a: 1 }, { a: 2, b: 3 });
//=> {a: 2, b: 3}
R.mergeDeepRight({ a: { b: 1 } }, { a: { c: 2 } });
//=> {a: {b: 1, c: 2}}
R.mergeWith(R.add, { a: 1 }, { a: 2, b: 3 });
//=> {a: 3, b: 3}
Keys and values
R.keys({ a: 1, b: 2 });
//=> ['a', 'b']
R.values({ a: 1, b: 2 });
//=> [1, 2]
R.toPairs({ a: 1, b: 2 });
//=> [['a', 1], ['b', 2]]
R.fromPairs([
["a", 1],
["b", 2],
]);
//=> {a: 1, b: 2}
Transform objects
R.evolve(
{
name: R.toUpper,
age: R.add(1),
},
{
name: "alice",
age: 30,
},
);
//=> {name: 'ALICE', age: 31}
R.clone({ a: { b: 1 } }); // deep copy
Lenses
Creating lenses
// Property lens
const nameLens = R.lensProp("name");
// Index lens
const headLens = R.lensIndex(0);
// Path lens
const streetLens = R.lensPath(["address", "street"]);
Using lenses
const person = { name: "Alice", age: 30 };
// view - get value
R.view(nameLens, person);
//=> 'Alice'
// set - replace value
R.set(nameLens, "Bob", person);
//=> {name: 'Bob', age: 30}
// over - transform value
R.over(nameLens, R.toUpper, person);
//=> {name: 'ALICE', age: 30}
Lens composition
const nested = {
data: {
users: [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
],
},
};
const firstUserNameLens = R.lensPath(["data", "users", 0, "name"]);
R.view(firstUserNameLens, nested);
//=> 'Alice'
R.set(firstUserNameLens, "Charlie", nested);
//=> {data: {users: [{name: 'Charlie', age: 30}, ...]}}
Logic and comparison
Compare values
R.equals({ a: 1 }, { a: 1 });
//=> true (deep equality)
R.identical(1, 1);
//=> true (reference ===)
R.gt(2, 1); //=> true
R.gte(2, 2); //=> true
R.lt(1, 2); //=> true
R.lte(2, 2); //=> true
Check values
R.isEmpty([]); //=> true
R.isEmpty({}); //=> true
R.isEmpty(""); //=> true
R.isNil(null); //=> true
R.isNil(undefined); //=> true
R.is(Number, 1); //=> true
R.is(Object, {}); //=> true
R.is(String, "s"); //=> true
Predicate combinators
R.both(isEven, isPositive)(4);
//=> true
R.either(isClub, isSpade)(card);
R.complement(R.isNil)(42);
//=> true
R.allPass([pred1, pred2, pred3])(val);
R.anyPass([pred1, pred2, pred3])(val);
Conditional logic
R.ifElse((x) => x > 0, R.inc, R.dec)(5); //=> 6
R.when(R.isNil, R.always(0))(null);
//=> 0
R.unless(Array.isArray, R.of)(42);
//=> [42]
R.defaultTo(42)(null); //=> 42
R.defaultTo(42)(false); //=> false
R.cond([
[R.equals(0), R.always("zero")],
[R.equals(100), R.always("hundred")],
[R.T, (x) => `other: ${x}`],
])(0); //=> 'zero'
String operations
Basic operations
R.split(",", "a,b,c");
//=> ['a', 'b', 'c']
R.join(",", ["a", "b"]);
//=> 'a,b'
R.toLower("ABC"); //=> 'abc'
R.toUpper("abc"); //=> 'ABC'
R.trim(" abc "); //=> 'abc'
Pattern matching
R.test(/^x/, "xyz");
//=> true
R.match(/[a-z]a/g, "banana");
//=> ['ba', 'na', 'na']
R.replace(/foo/, "bar", "foo foo");
//=> 'bar foo'
R.startsWith("a", "abc"); //=> true
R.endsWith("c", "abc"); //=> true
Math operations
Basic arithmetic
R.add(2, 3); //=> 5
R.subtract(10, 3); //=> 7
R.multiply(2, 3); //=> 6
R.divide(10, 2); //=> 5
R.inc(42); //=> 43
R.dec(42); //=> 41
R.negate(42); //=> -42
Aggregate functions
R.sum([1, 2, 3]); //=> 6
R.product([2, 3, 4]); //=> 24
R.mean([1, 2, 3, 4]); //=> 2.5
R.median([1, 2, 3]); //=> 2
Min, max, clamp
R.min(3, 5); //=> 3
R.max(3, 5); //=> 5
R.clamp(1, 10, -5); //=> 1
R.clamp(1, 10, 15); //=> 10
Modulo operations
R.modulo(17, 5); //=> 2
R.mathMod(-17, 5); //=> 3
Currying and functions
Curry functions
const add3 = R.curry((a, b, c) => a + b + c);
add3(1)(2)(3); //=> 6
add3(1, 2)(3); //=> 6
add3(1)(2, 3); //=> 6
const sumArgs = (...args) => R.sum(args);
R.curryN(3, sumArgs)(1)(2)(3); //=> 6
Partial application
const greet = (greeting, name) => `${greeting} ${name}`;
R.partial(greet, ["Hello"])("Alice");
//=> 'Hello Alice'
R.partialRight(greet, ["Alice"])("Hello");
//=> 'Hello Alice'
Argument manipulation
R.flip(R.divide)(2, 10);
//=> 5
R.unary(parseInt)("10", 2);
//=> 10 (ignores radix)
R.nAry(2, Math.max)(1, 2, 3);
//=> 2
Advanced combinators
// Average: sum / length
R.converge(R.divide, [R.sum, R.length])([1, 2, 3]);
//=> 2
// Apply multiple functions to same args
R.juxt([Math.min, Math.max])(3, 4, 9);
//=> [3, 9]
// Apply list of functions to list of values
R.ap([R.multiply(2), R.add(3)], [1, 2]);
//=> [2, 4, 4, 5]
// Memoization
R.memoizeWith(R.identity, expensiveFn);
// Run once
R.once(initFn);
Common recipes
Safe property access
const getStreet = R.pathOr("Unknown", ["address", "street"]);
getStreet({ address: { street: "Main St" } });
//=> 'Main St'
getStreet({});
//=> 'Unknown'
Transform API data
const processUsers = R.pipe(
R.prop("data"),
R.pluck("user"),
R.map(R.pick(["id", "name"])),
R.sortBy(R.prop("name")),
);
const result = processUsers(apiResponse);
Validate objects
const isValidUser = R.allPass([
R.propSatisfies(R.gt(R.__, 0), "age"),
R.propSatisfies(R.complement(R.isEmpty), "name"),
R.propIs(String, "email"),
]);
isValidUser({ age: 25, name: "Alice", email: "a@b.com" });
//=> true
Lens state updates
const nameLens = R.lensPath(["data", "user", "name"]);
const updateUserName = R.over(nameLens, R.toUpper);
updateUserName(state);
Ramda vs Lodash
| Feature | Ramda | Lodash |
|---|---|---|
| Currying | Automatic | Manual (_.curry()) |
| Argument order | Data last | Data first |
| Mutation | Never mutates | Sometimes mutates (_.pull, _.remove) |
| Composition | Built-in (pipe, compose) |
Less emphasis |
| Philosophy | Pure functional programming | General utility library |
| Point-free style | Encouraged | Not idiomatic |
| Learning curve | Steeper (FP concepts required) | Gentler |
Gotchas
Import changes in v0.25+
Wrong:
import R from "ramda"; // ❌ No default export
Correct:
import * as R from "ramda"; // ✅ Named namespace import
Default parameters with curry
// ❌ Doesn't work with curry
const fn = (a, b = 5) => a + b;
// ✅ Use curryN instead
const fn = R.curryN(2, (a, b = 5) => a + b);
Missing properties return undefined
R.path(["a", "b"], {}); //=> undefined
R.prop("x", {}); //=> undefined
// Use "Or" variants for defaults
R.pathOr("default", ["a", "b"], {}); //=> 'default'
R.propOr("default", "x", {}); //=> 'default'
Equality comparison
R.equals({ a: 1 }, { a: 1 }); //=> true (deep)
R.identical({ a: 1 }, { a: 1 }); //=> false (reference)
Tree-shaking
For optimal bundle size with Webpack, use:
npm install --save-dev babel-plugin-ramda
Performance considerations
Immutability has a cost for large datasets. Consider:
- Using transducers for large transformations
- Benchmarking critical paths
- Using mutable libraries (e.g., Immer) for complex state
Also see
- Ramda Official Site - Official documentation
- Ramda API Docs - Complete API reference
- Ramda GitHub - Source code and issues
- Ramda Cookbook - Common patterns and recipes
- Thinking in Ramda - Tutorial series
- Professor Frisby's Mostly Adequate Guide - Functional programming with JS