NexusCS

Ramda

JavaScript libraries
A quick reference to Ramda, a practical functional programming library for JavaScript with automatic currying, data-last arguments, and immutability.
featured

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