selfrefactor/rambda

Typescript focused FP library similar to Remeda and Ramda

TypeScript

1,758

2,279 commits

updated Sep 22, 2026

See the code

README

Rambda

Rambda is TypeScript-focused utility library similar to Remeda, Ramda and Radashi. - Documentation site

Commit activity Library size install size PR's Welcome GitHub contributors

❯ Example use

import { pipe, filter, map } from 'rambda'

const result = pipe(
  [1, 2, 3, 4],
  filter(x => x > 2),
  map(x => x * 2),
)
//=> [6, 8]

You can test this example in Rambda's REPL

---------------

❯ Rambda's features

❯ Goals

Typescript focus

Mixing Functional Programming and TypeScript is not easy.

One way to solve this is to focus what can be actually achieved and refrain from what is not possible.

R.pipe as the main way to use Rambda

  • All methods are meant to be used as part of R.pipe chain

  • This is the main purpose of functional programming, i.e. to pass data through a chain of functions.

  • Having R.pipe(input, ...fns) helps TypeScript to infer the types of the input and the output.

Here is one example why R.pipe is better than Ramda.pipe:

const list = [1, 2, 3];

it('within pipe', () => {
	const result = pipe(
		list,
		filter((x) => {
			x; // $ExpectType number
			return x > 1;
		}),
	);
	result; // $ExpectType number[]
});
it('within Ramda.pipe requires explicit types', () => {
	Ramda.pipe(
		(x) => x,
		filter<number>((x) => {
			x; // $ExpectType number
			return x > 1;
		}),
		filter((x: number) => {
			x; // $ExpectType number
			return x > 1;
		}),
	)(list);
});

❗ IMPORTANT - all methods are tested to deliver correct types when they are part of R.pipe/R.pipeAsync chains.

In other words:

R.filter(x => x > 1)([1,2,3])

might trigger TS error as it not the same as


R.pipe([1,2,3], R.filter(x => x > 1)

❗ All methods are curried

There is one way to use Rambda methods and it is with currying, i.e. using R.filter(fn, list) will not work as it is inteded to be R.filter(fn)(list).

The reason is that all methods are supposed to be used inside R.pipe. After all, building chains is the very base of functional programming.

Of course, there is value in supporting the case where you can pass all inputs at once, but I find that the price in terms of maintainability is not worth it.

Keep only the most useful methods

The idea is to give TypeScript users only the most useful methods and let them implement the rest. No magic logic methods that are hard to remember. You shouldn't need to read the documentation to understand what a method does. Its name and signature should be enough.

  • Methods that are simply to remember only by its name. Complex logic shouldn't be part of utility library, but part of your codebase.

  • Keep only methods which are both useful and which behaviour is obvious from its name. For example, R.innerJoin is kept, but R.identical, R.move is removed. Methods such as R.toLower, R.length provide little value. Such method are omitted from Rambda on purpose.

  • Some generic methods such as curry and assoc is not easy to be expressed in TypeScript. For this reason Rambda omits such methods.

  • No R.cond or R.ifElse as they make the chain less readable.

  • No R.length as it adds very little value.

  • No R.difference as user must remember the order of the inputs, i.e. which is compared to and which is compared against.

One way to use each method

Because of the focus on R.pipe, there is only one way to use each method. This helps with testing and also with TypeScript definitions.

  • All methods that 2 inputs, will have to be called with R.methodName(input1)(input2)
  • All methods that 3 inputs, will have to be called with R.methodName(input1, input2)(input3)

Deno support

import * as R from "https://deno.land/x/rambda/mod.ts";

R.filter(x => x > 1)([1, 2, 3])

Dot notation for R.path

Standard usage of R.path is R.path(['a', 'b'])({a: {b: 1} }).

In Rambda you have the choice to use dot notation(which is arguably more readable):

R.path('a.b')({a: {b: 1} })

Please note that since path input is turned into array, i.e. if you want R.path(['a','1', 'b'])({a: {'1': {b: 2}}}) to return 2, you will have to pass array path, not string path. If you pass a.1.b, it will turn path input to ['a', 1, 'b'].

Comma notation for R.pick and R.omit

Similar to dot notation, but the separator is comma(,) instead of dot(.).

R.pick('a,b', {a: 1 , b: 2, c: 3} })
// No space allowed between properties

Differences between Rambda and Ramda

Up until version 9.4.2, the aim of Rambda was to match as much as possible the Ramda API.

You can find documentation site of Rambda version 9.4.2 is here.

From version 10.0.0 onwards, Rambda is no longer aiming to be drop-in replacement for Ramda.

---------------

API

addProp


addProp<T extends object, P extends PropertyKey, V extends unknown>(
	prop: P,
	value: V
): (obj: T) => MergeTypes<T & Record<P, V>>

It adds new key-value pair to the object.

const result = R.pipe(
	{ a: 1, b: 'foo' },
	R.addProp('c', 3)
)
// => { a: 1, b: 'foo', c: 3 }

Try this R.addProp example in Rambda REPL

All TypeScript definitions
addProp<T extends object, P extends PropertyKey, V extends unknown>(
	prop: P,
	value: V
): (obj: T) => MergeTypes<T & Record<P, V>>;
R.addProp source
export function addProp(key, value) {
  return obj => ({ ...obj, [key]: value })
}
Tests
import { addProp } from './addProp'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 'foo' }, addProp('c', 3))
  expectTypeOf(result.a).toEqualTypeOf<number>()
  expectTypeOf(result.b).toEqualTypeOf<string>()
  expectTypeOf(result.c).toEqualTypeOf<number>()
  expect(result).toEqual({ a: 1, b: 'foo', c: 3 })
})

---------------

addPropToObjects


addPropToObjects<
  T extends object,
  K extends string,
  R
>(
	property: K,
  fn: (input: T) => R
): (list: T[]) => MergeTypes<T & { [P in K]: R }>[]

It receives list of objects and add new property to each item.

The value is based on result of fn function, which receives the current object as argument.

const result = R.pipe(
	[
		{a: 1, b: 2},
		{a: 3, b: 4},
	],
	R.addPropToObjects(
		'c',
		(x) => String(x.a + x.b),
	)
)
// => [{a: 1, b: 2, c: '3'}, {a: 3, b: 4, c: '7'}]

Try this R.addPropToObjects example in Rambda REPL

All TypeScript definitions
addPropToObjects<
  T extends object,
  K extends string,
  R
>(
	property: K,
  fn: (input: T) => R
): (list: T[]) => MergeTypes<T & { [P in K]: R }>[];
R.addPropToObjects source
import { mapFn } from './map.js'

export function addPropToObjects (
	property, 
	fn
){
	return listOfObjects => mapFn(
		(obj) => ({
			...(obj),
			[property]: fn(obj)
		}), 
		listOfObjects
	)
}
Tests
import { pipe } from './pipe'
import { addPropToObjects } from './addPropToObjects'

test('R.addPropToObjects', () => {
  let result = pipe(
    [
      {a: 1, b: 2},
      {a: 3, b: 4},
    ],
    addPropToObjects(
      'c',
      (x: { a: number; b: number }) => String(x.a + x.b),
    )
  )
  expectTypeOf(result).toEqualTypeOf<{ a: number; b: number; c: string }[]>()
  expect(result).toEqual([
    { a: 1, b: 2, c: '3' },
    { a: 3, b: 4, c: '7' },
  ])
})

---------------

all


all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true, if all members of array list returns true, when applied as argument to predicate function.

const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > -1

const result = R.pipe(
	list,
	R.all(predicate)
) // => true

Try this R.all example in Rambda REPL

All TypeScript definitions
all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.all source
export function all(predicate) {
  return list => {
    for (let i = 0; i < list.length; i++) {
      if (!predicate(list[i])) {
        return false
      }
    }

    return true
  }
}
Tests
import { all } from './all'
import { pipe } from './pipe'

const list = [0, 1, 2, 3, 4]

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    all((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 0
    }),
  )
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('when true', () => {
  const fn = (x: number) => x > -1

  expect(all(fn)(list)).toBeTruthy()
})

test('when false', () => {
  const fn = (x: number) => x > 2

  expect(all(fn)(list)).toBeFalsy()
})

---------------

allPass


allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F

It returns true, if all functions of predicates return true, when input is their argument.

const list = [[1, 2, 3, 4], [3, 4, 5]]
const result = R.pipe(
	list,
	R.filter(R.allPass([R.includes(2), R.includes(3)]))
) // => [[1, 2, 3, 4]]

Try this R.allPass example in Rambda REPL

All TypeScript definitions
allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F;
R.allPass source
export function allPass(predicates) {
  return input => {
    let counter = 0
    while (counter < predicates.length) {
      if (!predicates[counter](input)) {
        return false
      }
      counter++
    }

    return true
  }
}
Tests
import { allPass } from './allPass'
import { filter } from './filter'
import { pipe } from './pipe'

const list = [
  [1, 2, 3, 4],
  [3, 4, 5],
]

test('happy', () => {
  const result = pipe(list, filter(allPass([(x: number[]) => x.includes(2), (x: number[]) => x.includes(3)])))
  expectTypeOf(result).toEqualTypeOf<number[][]>()
  expect(result).toEqual([[1, 2, 3, 4]])
})

test('when returns false', () => {
  const result = pipe(list, filter(allPass([(x: number[]) => x.includes(12), (x: number[]) => x.includes(31)])))
  expect(result).toEqual([])
})

---------------

any


any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true, if at least one member of list returns true, when passed to a predicate function.

const list = [1, 2, 3]
const predicate = x => x * x > 8
R.any(predicate)(list)
// => true

Try this R.any example in Rambda REPL

All TypeScript definitions
any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.any source
export function any(predicate) {
  return list => {
    let counter = 0
    while (counter < list.length) {
      if (predicate(list[counter], counter)) {
        return true
      }
      counter++
    }

    return false
  }
}
Tests
import { any } from './any'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    any((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 2
    }),
  )
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

---------------

anyPass


anyPass<T, TF1 extends T, TF2 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2],
): (a: T) => a is TF1 | TF2

It accepts list of predicates and returns a function. This function with its input will return true, if any of predicates returns true for this input.

const isBig = x => x > 20
const isOdd = x => x % 2 === 1
const input = 11

const fn = R.anyPass(
  [isBig, isOdd]
)

const result = fn(input)
// => true

Try this R.anyPass example in Rambda REPL

All TypeScript definitions
anyPass<T, TF1 extends T, TF2 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2],
): (a: T) => a is TF1 | TF2;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3],
): (a: T) => a is TF1 | TF2 | TF3;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3],
): (a: T) => a is TF1 | TF2 | TF3;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3, (a: T) => a is TF4],
): (a: T) => a is TF1 | TF2 | TF3 | TF4;
...
...
R.anyPass source
export function anyPass(predicates) {
  return input => {
    let counter = 0
    while (counter < predicates.length) {
      if (predicates[counter](input)) {
        return true
      }
      counter++
    }
    return false
  }
}
Tests
import { anyPass } from "./anyPass"
import { filter } from "./filter"
import { pipe } from "./pipe"

test("happy", () => {
  const rules = [(x: any) => typeof x === "string", (x: any) => x > 10]
  const predicate = anyPass(rules)
  expect(predicate("foo")).toBeTruthy()
  expect(predicate(6)).toBeFalsy()
})

test("happy 2", () => {
  const rules = [(x: any) => typeof x === "string", (x: any) => x > 10]
  expect(anyPass(rules)(11)).toBeTruthy()
  expect(anyPass(rules)(undefined)).toBeFalsy()
})

const obj = {
  a: 1,
  b: 2,
}

test("when returns true", () => {
  const conditionArr = [(val: any) => val.a === 1, (val: any) => val.a === 2]
  expect(anyPass(conditionArr)(obj)).toBeTruthy()
})

test("when returns false", () => {
  const conditionArr = [(val: any) => val.a === 2, (val: any) => val.b === 3]
  expect(anyPass(conditionArr)(obj)).toBeFalsy()
})

test("with empty predicates list", () => {
	const result = pipe(3, anyPass([]))
	expectTypeOf(result).toEqualTypeOf<unknown>()
  expect(result).toBeFalsy()
})

test("issue #604", () => {
  const plusEq = (w: number, x: number, y: number, z: number) => w + x === y + z
  const result = anyPass([plusEq])(3, 3, 3, 3)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(false)
})

test("issue #642", () => {
  const isGreater = (num: number) => num > 5
  const pred = anyPass([isGreater])
  const xs = [0, 1, 2, 3]
  const filtered1 = filter(pred)(xs)
  expectTypeOf(filtered1).toEqualTypeOf<number[]>()
  const filtered2 = xs.filter(pred)
  expectTypeOf(filtered2).toEqualTypeOf<number[]>()
})

test("functions as a type guard", () => {
  const isString = (x: unknown): x is string => typeof x === "string"
  const isNumber = (x: unknown): x is number => typeof x === "number"
  const isBoolean = (x: unknown): x is boolean => typeof x === "boolean"
  const isStringNumberOrBoolean = anyPass([isString, isNumber, isBoolean])
  const aValue: unknown = 1
  if (isStringNumberOrBoolean(aValue)) {
    expectTypeOf(aValue).toEqualTypeOf<string | number | boolean>()
  }
})

---------------

append


append<T>(el: T): (list: readonly T[]) => T[]

It adds element x at the end of iterable.

const result = R.append('foo')(['bar', 'baz'])
// => ['bar', 'baz', 'foo']

Try this R.append example in Rambda REPL

All TypeScript definitions
append<T>(el: T): (list: readonly T[]) => T[];
append<T>(el: T): (list: T[]) => T[];
R.append source
import { cloneList } from './_internals/cloneList.js'

export function append(x) {
  return list => {
    const clone = cloneList(list)
    clone.push(x)

    return clone
  }
}
Tests
import { append } from './append'
import { prepend } from './prepend'
import { pipe } from './pipe'

const listOfNumbers = [1, 2, 3]

test('happy', () => {
  const result = pipe(listOfNumbers, append(4), prepend(0))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([0, 1, 2, 3, 4])
})

test('append to empty array', () => {
  expect(append('tests')([])).toEqual(['tests'])
})

test('with object', () => {
  const result = pipe([{ a: 1 }], append({ a: 10 }), prepend({ a: 20 }))
  expectTypeOf(result).toEqualTypeOf<{ a: number }[]>()
  expect(result).toEqual([{ a: 20 }, { a: 1 }, { a: 10 }])
})

---------------

ascend


ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering

Helper function to be used with R.sort to sort list in ascending order.

const result = R.pipe(
	[{a: 1}, {a: 2}, {a: 0}],
	R.sort(R.ascend(R.prop('a')))
)
// => [{a: 0}, {a: 1}, {a: 2}]

Try this R.ascend example in Rambda REPL

All TypeScript definitions
ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
R.ascend source
export function createCompareFunction(a, b, winner, loser) {
  if (a === b) {
    return 0
  }

  return a < b ? winner : loser
}

export function ascend(getFunction) {
	return (a, b) => {
  const aValue = getFunction(a)
  const bValue = getFunction(b)

  return createCompareFunction(aValue, bValue, -1, 1)
}
}
Tests
import { ascend } from './ascend'
import { descend } from './descend'
import { sort } from './sort'

test('ascend', () => {
  const result = sort(
    ascend((x: { a: number }) => x.a))(
    [{a:1}, {a:3}, {a:2}],
  )
  expectTypeOf(result).toEqualTypeOf<{ a: number }[]>()
  expect(result).toEqual([{a:1}, {a:2}, {a:3}])
})

test('descend', () => {
  const result = sort(
    descend((x: { a: number }) => x.a))(
    [{a:1}, {a:3}, {a:2}],
  )
  expect(result).toEqual([{a:3}, {a:2}, {a:1}])
})

---------------

assertType


assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U

It helps to make sure that input is from specific type. Similar to R.convertToType, but it actually checks the type of the input value. If fn input returns falsy value, then the function will throw an error.

All TypeScript definitions
assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U;
assertType<T>(fn: (x: T) => boolean): (x: T) => T;
R.assertType source
export function assertType(fn) {
  return (x) => {
    if (fn(x)) {
      return x
    }
    throw new Error('type assertion failed in R.assertType')
  }
}
Tests
import { assertType } from './assertType'
import { pipe } from './pipe'

type Book = {
  title: string
  year: number
}

type BookToRead = Book & {
  bookmarkFlag: boolean
}

function isBookToRead(book: Book): book is BookToRead {
  return (book as BookToRead).bookmarkFlag !== undefined
}

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    assertType((x: number[]) => x.length === 3),
  )
  expect(result).toEqual([1, 2, 3])
})

test('throw', () => {
  expect(() => {
    pipe(
      [1, 2, 3],
      assertType((x: number[]) => x.length === 4),
    )
  }).toThrow('type assertion failed in R.assertType')
})

test('R.assertType', () => {
  const result = pipe(
    { title: 'Book1', year: 2020, bookmarkFlag: true },
    assertType(isBookToRead),
  )
  expectTypeOf(result).toEqualTypeOf<BookToRead>()
  expect(result).toEqual({ title: 'Book1', year: 2020, bookmarkFlag: true })
})

---------------

checkObjectWithSpec


checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean

It returns true if all each property in conditions returns true when applied to corresponding property in input object.

const condition = R.checkObjectWithSpec({
  a : x => typeof x === "string",
  b : x => x === 4
})
const input = {
  a : "foo",
  b : 4,
  c : 11,
}

const result = condition(input)
// => true

Try this R.checkObjectWithSpec example in Rambda REPL

All TypeScript definitions
checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean;
R.checkObjectWithSpec source
export function checkObjectWithSpec(conditions) {
  return input => {
    let shouldProceed = true
    for (const prop in conditions) {
      if (!shouldProceed) {
        continue
      }
      const result = conditions[prop](input[prop])
      if (shouldProceed && result === false) {
        shouldProceed = false
      }
    }

    return shouldProceed
  }
}
Tests
import { checkObjectWithSpec } from './checkObjectWithSpec'
import { equals } from './equals'

test('happy', () => {
  const input = {
    a: 'foo',
    b: 'bar',
    x: 11,
    y: 19,
  }
  const conditions = {
    a: equals('foo'),
    b: equals('bar'),
  }
  const result = checkObjectWithSpec(conditions)(input)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('when true', () => {
  const result = checkObjectWithSpec({
    a: equals('foo'),
    b: equals('bar'),
  })({
    a: 'foo',
    b: 'bar',
    x: 11,
    y: 19,
  })
  expect(result).toBeTruthy()
})

test('when false | early exit', () => {
  let counter = 0
  const equalsFn = (expected: string) => (input: string) => {
    counter++
    return input === expected
  }
  const predicate = checkObjectWithSpec({
    a: equalsFn('foo'),
    b: equalsFn('baz'),
  })
  expect(
    predicate({
      a: 'notfoo',
      b: 'notbar',
    }),
  ).toBeFalsy()
  expect(counter).toBe(1)
})

---------------

compact


compact<T>(list: T[]): Array<StrictNonNullable<T>>

It removes null and undefined members from list or object input.

const result = R.pipe(
	{
		a: [ undefined, '', 'a', 'b', 'c'],
		b: [1,2, null, 0, undefined, 3],
		c: { a: 1, b: 2, c: 0, d: undefined, e: null, f: false },
	},
	x => ({
		a: R.compact(x.a),
		b: R.compact(x.b),
		c: R.compact(x.c)
	})
)
// => { a: ['a', 'b', 'c'], b: [1, 2, 3], c: { a: 1, b: 2, c: 0, f: false } }

Try this R.compact example in Rambda REPL

All TypeScript definitions
compact<T>(list: T[]): Array<StrictNonNullable<T>>;
compact<T extends object>(record: T): {
  [K in keyof T as Exclude<T[K], null | undefined> extends never
    ? never
    : K
  ]: Exclude<T[K], null | undefined>
};
R.compact source
import { isArray } from './_internals/isArray.js'
import { reject } from './reject.js'
import { rejectObject } from './rejectObject.js'

const isNullOrUndefined = x => x === null || x === undefined

export function compact(input){
	if(isArray(input)){
		return reject(isNullOrUndefined)(input)
	}
	return rejectObject(isNullOrUndefined)(input)
}
Tests
import { compact } from './compact'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(
    {
      a: [ undefined, '', 'a', 'b', 'c', null ],
      b: [1,2, null, 0, undefined, 3],
      c: { a: 1, b: 2, c: 0, d: undefined, e: null, f: false },
    },
    (x: { a: (string | undefined | null)[]; b: (number | null | undefined)[]; c: { a: number; b: number; c: number; d: undefined; e: null; f: boolean } }) => ({
      a: compact(x.a),
      b: compact(x.b),
      c: compact(x.c),
    })
  )
  expectTypeOf(result.a).toEqualTypeOf<string[]>()
  expectTypeOf(result.b).toEqualTypeOf<number[]>()
  expectTypeOf(result.c).toEqualTypeOf<{ a: number; b: number; c: number; f: boolean }>()
  expect(result.a).toEqual(['', 'a', 'b', 'c'])
  expect(result.b).toEqual([1,2,0,3])
  expect(result.c).toEqual({ a: 1, b: 2, c: 0, f: false })
})

---------------

complement


complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean

It returns inverted version of origin function that accept input as argument.

The return value of inverted is the negative boolean value of origin(input).

const fn = x => x > 5
const inverted = complement(fn)

const result = [
  fn(7),
  inverted(7)
] => [ true, false ]

Try this R.complement example in Rambda REPL

All TypeScript definitions
complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean;
R.complement source
export function complement(fn) {
  return (...input) => !fn(...input)
}
Tests
import { complement } from './complement'

test('happy', () => {
  const fn = complement((x: number) => x > 10)
  const result = fn(1)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('with multiple parameters', () => {
  const between = (a: number, b: number, c: number) => a < b && b < c
  const f = complement(between)
  expect(f(4, 5, 11)).toBeFalsy()
  expect(f(12, 2, 6)).toBeTruthy()
})

---------------

concat


concat<T>(x: T[]): (y: T[]) => T[]

It returns a new string or array, which is the result of merging x and y.

R.concat([1, 2])([3, 4]) // => [1, 2, 3, 4]
R.concat('foo')('bar') // => 'foobar'

Try this R.concat example in Rambda REPL

All TypeScript definitions
concat<T>(x: T[]): (y: T[]) => T[];
concat(x: string): (y: string) => string;
R.concat source
export function concat(x) {
  return y => (typeof x === 'string' ? `${x}${y}` : [...x, ...y])
}
Tests
import { concat } from './concat'
import { pipe } from './pipe'

const list1 = [1, 2, 3]
const list2 = [4, 5, 6]

test('R.concat', () => {
  const result = pipe(list1, concat(list2))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([4, 5, 6, 1, 2, 3])
  const resultString = pipe('foo', concat('list2'))
  expectTypeOf(resultString).toEqualTypeOf<string>()
  expect(resultString).toBe('list2foo')
})

---------------

convertToType


convertToType<T>(x: unknown) : T

It helps to convert a value to a specific type. It is useful when you have to overcome TypeScript's type inference.

All TypeScript definitions
convertToType<T>(x: unknown) : T;
R.convertToType source
export function convertToType(x) {
  return x
}

---------------

count


count<T>(predicate: (x: T) => boolean): (list: T[]) => number

It counts how many times predicate function returns true, when supplied with iteration of list.

const list = [{a: 1}, 1, {a:2}]
const result = R.count(x => x.a !== undefined)(list)
// => 2

Try this R.count example in Rambda REPL

All TypeScript definitions
count<T>(predicate: (x: T) => boolean): (list: T[]) => number;
R.count source
import { isArray } from './_internals/isArray.js'

export function count(predicate) {
  return list => {
    if (!isArray(list)) {
      return 0
    }

    return list.filter(x => predicate(x)).length
  }
}
Tests
import { count } from './count'
import { pipe } from './pipe'

const predicate = (x: any) => x.a !== undefined

test('happy', () => {
  const list = [1, 2, 3]
  const result = pipe(list, count((x: number) => x > 1))
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

test('with empty list', () => {
  expect(count(predicate)([])).toBe(0)
})

---------------

countBy


countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number }

It counts elements in a list after each instance of the input list is passed through transformFn function.

const list = [ 'a', 'A', 'b', 'B', 'c', 'C' ]

const result = countBy(x => x.toLowerCase())( list)
const expected = { a: 2, b: 2, c: 2 }
// => `result` is equal to `expected`

Try this R.countBy example in Rambda REPL

All TypeScript definitions
countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number };
R.countBy source
export function countBy(fn) {
  return list => {
    const willReturn = {}

    list.forEach(item => {
      const key = fn(item)
      if (!willReturn[key]) {
        willReturn[key] = 1
      } else {
        willReturn[key]++
      }
    })

    return willReturn
  }
}
Tests
import { countBy } from './countBy'
import { pipe } from './pipe'

const list = ['a', 'A', 'b', 'B', 'c', 'C']

test('happy', () => {
  const result = pipe(
    list,
    countBy((x: string) => x.toLowerCase()),
  )
  expectTypeOf(result.a).toEqualTypeOf<number>()
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expectTypeOf(result).toEqualTypeOf<{ [index: string]: number }>()
  expect(result).toEqual({ a: 2, b: 2, c: 2 })
})

---------------

createObjectFromKeys


createObjectFromKeys<const K extends readonly PropertyKey[], V>(
	fn: (key: K[number]) => V
): (keys: K) => { [P in K[number]]: V }
const result = R.createObjectFromKeys(
	(x, index) => `${x}-${index}`
)(['a', 'b', 'c'])
// => {a: 'a-0', b: 'b-1', c: 'c-2'}

Try this R.createObjectFromKeys example in Rambda REPL

All TypeScript definitions
createObjectFromKeys<const K extends readonly PropertyKey[], V>(
	fn: (key: K[number]) => V
): (keys: K) => { [P in K[number]]: V };
createObjectFromKeys<const K extends readonly PropertyKey[], V>(
	fn: (key: K[number], index: number) => V
): (keys: K) => { [P in K[number]]: V };
R.createObjectFromKeys source
export function createObjectFromKeys(fn) {
	return keys => {
		const result = {}
		keys.forEach((key, index) => {
			result[key] = fn(key, index)
		})

		return result
	}
}
Tests
import { createObjectFromKeys } from './createObjectFromKeys'

test('happy', () => {
  const result = createObjectFromKeys<['a', 'b'], string>((key: string, index: number) => key.toUpperCase() + index)(['a', 'b'])
  const expected = { a: 'A0', b: 'B1' }
  expect(result).toEqual(expected)
})

---------------

defaultTo


defaultTo<T>(defaultValue: T): (input: unknown) => T

It returns defaultValue, if all of inputArguments are undefined, null or NaN.

Else, it returns the first truthy inputArguments instance(from left to right).

💥 Typescript Note: Pass explicit type annotation when used with R.pipe/R.compose for better type inference

R.defaultTo('foo')('bar') // => 'bar'
R.defaultTo('foo'))(undefined) // => 'foo'

// Important - emtpy string is not falsy value
R.defaultTo('foo')('') // => 'foo'

Try this R.defaultTo example in Rambda REPL

All TypeScript definitions
defaultTo<T>(defaultValue: T): (input: unknown) => T;
R.defaultTo source
function isFalsy(input) {
  return input === undefined || input === null || Number.isNaN(input) === true
}

export function defaultTo(defaultArgument) {
  return input => isFalsy(input) ? defaultArgument : input
}
Tests
import { defaultTo } from './defaultTo'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe('bar' as unknown, defaultTo('foo'))
  expectTypeOf(result).toEqualTypeOf<string>()
  expect(result).toBe('bar')
})

test('with undefined', () => {
  expect(defaultTo('foo')(undefined)).toBe('foo')
})

test('with null', () => {
  expect(defaultTo('foo')(null)).toBe('foo')
})

test('with NaN', () => {
  expect(defaultTo('foo')(Number.NaN)).toBe('foo')
})

test('with empty string', () => {
  expect(defaultTo('foo')('')).toBe('')
})

test('with false', () => {
  expect(defaultTo('foo')(false)).toBeFalsy()
})

test('when inputArgument passes initial check', () => {
  expect(defaultTo('foo')('bar')).toBe('bar')
})

---------------

delay


delay(ms: number): Promise<'RAMBDA_DELAY'>

setTimeout as a promise that resolves to RAMBDA_DELAY string after ms milliseconds.

All TypeScript definitions
delay(ms: number): Promise<'RAMBDA_DELAY'>;
R.delay source
export const RAMBDA_DELAY = 'RAMBDA_DELAY'

export function delay(ms) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(RAMBDA_DELAY)
    }, ms)
  })
}

---------------

descend


descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering

Helper function to be used with R.sort to sort list in descending order.

const result = R.pipe(
	[{a: 1}, {a: 2}, {a: 0}],
	R.sort(R.descend(R.prop('a')))
)
// => [{a: 2}, {a: 1}, {a: 0}]

Try this R.descend example in Rambda REPL

All TypeScript definitions
descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
R.descend source
import { createCompareFunction } from './ascend.js'

export function descend(getFunction) {
  return (a, b) => {
    const aValue = getFunction(a)
    const bValue = getFunction(b)

    return createCompareFunction(aValue, bValue, 1, -1)
  }
}

---------------

difference


difference<T>(x: T[]): (y: T[]) => T[]

It returns a merged list of x and y with all equal elements removed.

R.equals is used to determine equality.

const x = [ 1, 2, 3, 4 ]
const y = [ 3, 4, 5, 6 ]

const result = R.difference(x)(y)
// => [ 1, 2, 5, 6 ]

Try this R.difference example in Rambda REPL

All TypeScript definitions
difference<T>(x: T[]): (y: T[]) => T[];
R.difference source
import { filter } from './filter.js'
import { excludes } from './excludes.js'

export function difference(listA) {
	return listB => ([
		...filter(value => excludes(listB)(value))(listA),
		...filter(value => excludes(listA)(value))(listB),
	])
}
Tests
import { difference } from './difference'

test('difference', () => {
  const list1 = [1, 2, 3, 4]
  const list2 = [3, 4, 5, 6]
  expect(difference(list1)(list2)).toEqual([1, 2, 5, 6])
  expect(difference([])([])).toEqual([])
})

test('difference with objects', () => {
  const list1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
  const list2 = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]
  const result = difference(list1)(list2)
  expectTypeOf(result).toEqualTypeOf<{ id: number }[]>()
  expect(result).toEqual([
    { id: 1 },
    { id: 2 },
    { id: 5 },
    { id: 6 },
  ])
})

---------------

drop


drop<T>(howMany: number): (list: T[]) => T[]

It returns howMany items dropped from beginning of list.

R.drop(2)(['foo', 'bar', 'baz']) // => ['baz']

Try this R.drop example in Rambda REPL

All TypeScript definitions
drop<T>(howMany: number): (list: T[]) => T[];
R.drop source
export function drop(howManyToDrop) {
  return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0)
}
Tests
import { drop } from './drop'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([1, 2, 3, 4], drop(2))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([3, 4])
})

test('with array', () => {
  expect(drop(2)(['foo', 'bar', 'baz'])).toEqual(['baz'])
  expect(drop(3)(['foo', 'bar', 'baz'])).toEqual([])
  expect(drop(4)(['foo', 'bar', 'baz'])).toEqual([])
})

test('with non-positive count', () => {
  expect(drop(0)([1, 2, 3])).toEqual([1, 2, 3])
  expect(drop(-1)([1, 2, 3])).toEqual([1, 2, 3])
  expect(drop(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3])
})

---------------

dropLast


dropLast<T>(howMany: number): (list: T[]) => T[]

It returns howMany items dropped from the end of list.

All TypeScript definitions
dropLast<T>(howMany: number): (list: T[]) => T[];
R.dropLast source
export function dropLast(numberItems) {
  return list => (numberItems > 0 ? list.slice(0, -numberItems) : list.slice())
}
Tests
import { dropLast } from './dropLast'

test('with array', () => {
  expect(dropLast(2)(['foo', 'bar', 'baz'])).toEqual(['foo'])
  expect(dropLast(3)(['foo', 'bar', 'baz'])).toEqual([])
  expect(dropLast(4)(['foo', 'bar', 'baz'])).toEqual([])
})

test('with non-positive count', () => {
  expect(dropLast(0)([1, 2, 3])).toEqual([1, 2, 3])
  expect(dropLast(-1)([1, 2, 3])).toEqual([1, 2, 3])
  expect(dropLast(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3])
})

---------------

dropLastWhile


dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]
const list = [1, 2, 3, 4, 5];
const predicate = x => x >= 3

const result = dropLastWhile(predicate)(list);
// => [1, 2]

Try this R.dropLastWhile example in Rambda REPL

All TypeScript definitions
dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropLastWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
R.dropLastWhile source
export function dropLastWhile(predicate) {
  return list => {
    if (list.length === 0) {
      return list
    }

    const toReturn = []
    let counter = list.length

    while (counter) {
      const item = list[--counter]
      if (!predicate(item, counter)) {
        toReturn.push(item)
        break
      }
    }

    while (counter) {
      toReturn.push(list[--counter])
    }

    return toReturn.reverse()
  }
}
Tests
import { dropLastWhile } from './dropLastWhile'

const list = [1, 2, 3, 4, 5]

test('with list', () => {
  const result = dropLastWhile((x: number) => x >= 3)(list)
  expect(result).toEqual([1, 2])
})

test('with empty list', () => {
  expect(dropLastWhile(() => true)([])).toEqual([])
})

---------------

dropRepeatsBy


dropRepeatsBy<T, U>(fn: (x: T) => U): (list: T[]) => T[]
const result = R.dropRepeatsBy(
  Math.abs,
  [1, -1, 2, 3, -3]
)
// => [1, 2, 3]

Try this R.dropRepeatsBy example in Rambda REPL

All TypeScript definitions
dropRepeatsBy<T, U>(fn: (x: T) => U): (list: T[]) => T[];

---------------

dropRepeatsWith


dropRepeatsWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[]
const list = [{a:1,b:2}, {a:1,b:3}, {a:2, b:4}]
const result = R.dropRepeatsWith(R.prop('a'))(list)

// => [{a:1,b:2}, {a:2, b:4}]

Try this R.dropRepeatsWith example in Rambda REPL

All TypeScript definitions
dropRepeatsWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[];

---------------

dropWhile


dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]
const list = [1, 2, 3, 4]
const predicate = x => x < 3
const result = R.dropWhile(predicate)(list)
// => [3, 4]

Try this R.dropWhile example in Rambda REPL

All TypeScript definitions
dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
R.dropWhile source
export function dropWhile(predicate) {
  return iterable => {
    const toReturn = []
    let counter = 0

    while (counter < iterable.length) {
      const item = iterable[counter++]
      if (!predicate(item, counter)) {
        toReturn.push(item)
        break
      }
    }

    while (counter < iterable.length) {
      toReturn.push(iterable[counter++])
    }

    return toReturn
  }
}
Tests
import { dropWhile } from './dropWhile'
import { pipe } from './pipe'

const list = [1, 2, 3, 4]

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    dropWhile((x: number) => x < 3),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([3])
})

test('always false', () => {
  const predicate = () => false
  const result = dropWhile(predicate)(list)
  expect(result).toEqual(list)
})

test('with index', () => {
  const result = pipe(
    [1, 2, 3],
    dropWhile((x: number, i: number) => {
      expectTypeOf(i).toEqualTypeOf<number>()
      return x + i > 2
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 2, 3])
})

---------------

duplicateBy


duplicateBy<T, U>(fn: (x: T) => U): (list: T[]) => T[]
const list = [{a:1}, {a:2}, {a:1}]
const result = R.duplicateBy(x => x)(list)

// => [{a:1}]

Try this R.duplicateBy example in Rambda REPL

All TypeScript definitions
duplicateBy<T, U>(fn: (x: T) => U): (list: T[]) => T[];
R.duplicateBy source
import { _Set } from '../src/_internals/set.js'

export function duplicateBy(fn) {
  return list => {
    const set = new _Set()

    return list.filter(item => !set.checkUniqueness(fn(item)))
  }
}
Tests
import { duplicateBy } from './duplicateBy'

test('happy', () => {
  expect(duplicateBy(Math.abs)([-2, -1, 0, 1, 2])).toEqual([1, 2])
})

test('returns an empty array for an empty array', () => {
  expect(duplicateBy(Math.abs)([])).toEqual([])
})

test('uses R.uniq', () => {
  const list = [{ a: 1 }, { a: 2 }, { a: 1 }]
  const expected = [{ a: 1 }]
  expect(duplicateBy((x: any) => x)(list)).toEqual(expected)
})

---------------

eqBy


eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean
const result = R.eqBy(Math.abs, 5)(-5)
// => true

Try this R.eqBy example in Rambda REPL

All TypeScript definitions
eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean;
R.eqBy source
import { equalsFn } from './equals.js'

export function eqBy(fn, a) {
  return b => equalsFn(fn(a), fn(b))
}
Tests
import { eqBy } from './eqBy'

test('deteremines whether two values map to the same value in the codomain', () => {
  expect(eqBy<number>(Math.abs, 5)(5)).toBe(true)
  expect(eqBy<number>(Math.abs, 5)(-5)).toBe(true)
  expect(eqBy<number>(Math.abs, -5)(5)).toBe(true)
  expect(eqBy<number>(Math.abs, -5)(-5)).toBe(true)
  expect(eqBy<number>(Math.abs, 42)(99)).toBe(false)
})

test('has R.equals semantics', () => {
  expect(eqBy(Math.abs, Number.NaN)(Number.NaN)).toBe(true)
  expect(eqBy((x: number[]) => x.length, [42])([42])).toBe(true)
  expect(eqBy((x: any) => x, { a: 1 })({ a: 1 })).toBe(true)
  expect(eqBy((x: any) => x, { a: 1 })({ a: 2 })).toBe(false)
})

---------------

eqProps


eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean

It returns true if property prop in obj1 is equal to property prop in obj2 according to R.equals.

const obj1 = {a: 1, b:2}
const obj2 = {a: 1, b:3}
const result = R.eqProps('a', obj1)(obj2)
// => true

Try this R.eqProps example in Rambda REPL

All TypeScript definitions
eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean;
R.eqProps source
import { equalsFn } from './equals.js'

export function eqProps(property, objA) {
  return objB => equalsFn( objA[property], objB[property] )
}
Tests
import { eqProps } from './eqProps'
import { pipe } from './pipe'

const obj1 = {
  a: 1,
  b: 2,
}
const obj2 = {
  a: 1,
  b: 3,
}

test('happy', () => {
  const objA = { a: { b: 1 }, c: 2 }
  const objB = { a: { b: 1 }, c: 3 }
  const result = pipe(objA, eqProps('a', objB))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('props are equal', () => {
  const result = eqProps('a', obj1)(obj2)
  expect(result).toBeTruthy()
})

test('props are not equal', () => {
  const result = eqProps('b', obj1)(obj2)
  expect(result).toBeFalsy()
})

test('prop does not exist', () => {
  const result = eqProps('c' as 'a' | 'b', obj1)(obj2)
  expect(result).toBeTruthy()
})

---------------

equals


equals<T>(x: T): (y: T) => boolean

It deeply compares x and y and returns true if they are equal.

💥 It doesn't handle cyclical data structures and functions

R.equals(
  [1, {a:2}, [{b: 3}]],
  [1, {a:2}, [{b: 3}]]
) // => true

Try this R.equals example in Rambda REPL

All TypeScript definitions
equals<T>(x: T): (y: T) => boolean;
R.equals source
import { isArray } from './_internals/isArray.js'
import { type } from './type.js'

export function _lastIndexOf(valueToFind, list) {
  if (!isArray(list)) {
    throw new Error(`Cannot read property 'indexOf' of ${list}`)
  }

  const typeOfValue = type(valueToFind)
  if (!['Array', 'NaN', 'Object', 'RegExp'].includes(typeOfValue)) {
    return list.lastIndexOf(valueToFind)
  }

  const { length } = list
  let index = length
  let foundIndex = -1

  while (--index > -1 && foundIndex === -1) {
    if (equalsFn(list[index], valueToFind)) {
      foundIndex = index
    }
  }

  return foundIndex
}

export function _indexOf(valueToFind, list) {
  if (!isArray(list)) {
    throw new Error(`Cannot read property 'indexOf' of ${list}`)
  }

  const typeOfValue = type(valueToFind)
  if (!['Array', 'NaN', 'Object', 'RegExp'].includes(typeOfValue)) {
    return list.indexOf(valueToFind)
  }

  let index = -1
  let foundIndex = -1
  const { length } = list

  while (++index < length && foundIndex === -1) {
    if (equalsFn(list[index], valueToFind)) {
      foundIndex = index
    }
  }

  return foundIndex
}

function _arrayFromIterator(iter) {
  const list = []
  let next
  while (!(next = iter.next()).done) {
    list.push(next.value)
  }

  return list
}

function _compareSets(a, b) {
  if (a.size !== b.size) {
    return false
  }

  const aList = _arrayFromIterator(a.values())
  const bList = _arrayFromIterator(b.values())

  const filtered = aList.filter(aInstance => _indexOf(aInstance, bList) === -1)

  return filtered.length === 0
}

function compareErrors(a, b) {
  if (a.message !== b.message) {
    return false
  }
  if (a.toString !== b.toString) {
    return false
  }

  return a.toString() === b.toString()
}

function parseDate(maybeDate) {
  if (!maybeDate.toDateString) {
    return [false]
  }

  return [true, maybeDate.getTime()]
}

function parseRegex(maybeRegex) {
  if (maybeRegex.constructor !== RegExp) {
    return [false]
  }

  return [true, maybeRegex.toString()]
}

export function equalsFn(a, b) {
  if (Object.is(a, b)) {
    return true
  }

  const aType = type(a)

  if (aType !== type(b)) {
    return false
  }
  if (aType === 'Function') {
    return a.name === undefined ? false : a.name === b.name
  }

  if (['NaN', 'Null', 'Undefined'].includes(aType)) {
    return true
  }

  if (['BigInt', 'Number'].includes(aType)) {
    if (Object.is(-0, a) !== Object.is(-0, b)) {
      return false
    }

    return a.toString() === b.toString()
  }

  if (['Boolean', 'String'].includes(aType)) {
    return a.toString() === b.toString()
  }

  if (aType === 'Array') {
    const aClone = Array.from(a)
    const bClone = Array.from(b)

    if (aClone.toString() !== bClone.toString()) {
      return false
    }

    let loopArrayFlag = true
    aClone.forEach((aCloneInstance, aCloneIndex) => {
      if (loopArrayFlag) {
        if (
          aCloneInstance !== bClone[aCloneIndex] &&
          !equalsFn(aCloneInstance, bClone[aCloneIndex])
        ) {
          loopArrayFlag = false
        }
      }
    })

    return loopArrayFlag
  }

  const aRegex = parseRegex(a)
  const bRegex = parseRegex(b)

  if (aRegex[0]) {
    return bRegex[0] ? aRegex[1] === bRegex[1] : false
  }
  if (bRegex[0]) {
    return false
  }

  const aDate = parseDate(a)
  const bDate = parseDate(b)

  if (aDate[0]) {
    return bDate[0] ? aDate[1] === bDate[1] : false
  }
  if (bDate[0]) {
    return false
  }

  if (a instanceof Error) {
    if (!(b instanceof Error)) {
      return false
    }

    return compareErrors(a, b)
  }

  if (aType === 'Set') {
    return _compareSets(a, b)
  }

  if (aType === 'Object') {
    const aKeys = Object.keys(a)

    if (aKeys.length !== Object.keys(b).length) {
      return false
    }

    let loopObjectFlag = true
    aKeys.forEach(aKeyInstance => {
      if (loopObjectFlag) {
        const aValue = a[aKeyInstance]
        const bValue = b[aKeyInstance]

        if (aValue !== bValue && !equalsFn(aValue, bValue)) {
          loopObjectFlag = false
        }
      }
    })

    return loopObjectFlag
  }

  return false
}
export function equals(a) {
  return b => equalsFn(a, b)
}
Tests
import { equals } from './equals'

test('happy', () => {
  const result = equals(4)(1)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(false)

  const foo = { a: 1 }
  const bar = { a: 2 }
  const result2 = equals(foo)(bar)
  expectTypeOf(result2).toEqualTypeOf<boolean>()
  expect(result2).toBe(false)
})

test('compare functions', () => {
  function foo() {}
  function bar() {}
  const baz = () => {}

  const expectTrue = equals(foo)(foo)
  const expectFalseFirst = equals(bar)(foo)
  const expectFalseSecond = equals(baz)(foo)

  expect(expectTrue).toBeTruthy()
  expect(expectFalseFirst).toBeFalsy()
  expect(expectFalseSecond).toBeFalsy()
})

test('with array of objects', () => {
  const list1 = [{ a: 1 }, [{ b: 2 }]]
  const list2 = [{ a: 1 }, [{ b: 2 }]]
  const list3 = [{ a: 1 }, [{ b: 3 }]]

  expect(equals(list1)(list2)).toBeTruthy()
  expect(equals(list1)(list3)).toBeFalsy()
})

test('with regex', () => {
  expect(equals(/s/)(/s/)).toBeTruthy()
  expect(equals(/s/)(/d/)).toBeFalsy()
  expect(equals(/a/gi)(/a/gi)).toBeTruthy()
  expect(equals(/a/gim)(/a/gim)).toBeTruthy()
  expect(equals(/a/gi)(/a/i)).toBeFalsy()
})

test('not a number', () => {
  expect(equals([Number.NaN])([Number.NaN])).toBeTruthy()
})

test('new number', () => {
  expect(equals(new Number(0))(new Number(0))).toBeTruthy()
  expect(equals(new Number(0))(new Number(1))).toBeFalsy()
  expect(equals(new Number(1))(new Number(0))).toBeFalsy()
})

test('new string', () => {
  expect(equals(new String(''))(new String(''))).toBeTruthy()
  expect(equals(new String(''))(new String('x'))).toBeFalsy()
  expect(equals(new String('x'))(new String(''))).toBeFalsy()
  expect(equals(new String('foo'))(new String('foo'))).toBeTruthy()
  expect(equals(new String('foo'))(new String('bar'))).toBeFalsy()
  expect(equals(new String('bar'))(new String('foo'))).toBeFalsy()
})

test('new Boolean', () => {
  expect(equals(new Boolean(true))(new Boolean(true))).toBeTruthy()
  expect(equals(new Boolean(false))(new Boolean(false))).toBeTruthy()
  expect(equals(new Boolean(true))(new Boolean(false))).toBeFalsy()
  expect(equals(new Boolean(false))(new Boolean(true))).toBeFalsy()
})

test('new Error', () => {
  expect(equals(new Error('XXX'))({} as any)).toBeFalsy()
  expect(equals(new Error('XXX'))(new TypeError('XXX'))).toBeFalsy()
  expect(equals(new Error('XXX'))(new Error('YYY'))).toBeFalsy()
  expect(equals(new Error('XXX'))(new Error('XXX'))).toBeTruthy()
  expect(equals(new Error('XXX'))(new TypeError('YYY'))).toBeFalsy()
})

test('with dates', () => {
  expect(equals(new Date(0))(new Date(0))).toBeTruthy()
  expect(equals(new Date(1))(new Date(1))).toBeTruthy()
  expect(equals(new Date(0))(new Date(1))).toBeFalsy()
  expect(equals(new Date(1))(new Date(0))).toBeFalsy()
  expect(equals(new Date(0))({} as any)).toBeFalsy()
  expect(equals({})(new Date(0))).toBeFalsy()
})

test('ramda spec', () => {
  expect(equals({})({})).toBeTruthy()

  expect(
    equals(
      {
        a: 1,
        b: 2,
      })({
        a: 1,
        b: 2,
      }),
     )
  .toBeTruthy()

  expect(
    equals(
      {
        a: 2,
        b: 3,
      })(
      {
        a: 2,
        b: 3,
      } as any
    )
  ).toBeTruthy()

  expect(
    equals(
      {
        a: 2,
        b: 3,
      })(
      {
        a: 3,
        b: 3,
      } as any
    ),
  ).toBeFalsy()

  expect(
    equals(
      {
        a: 2,
        b: 3,
        c: 1,
      })(
      {
        a: 2,
        b: 3,
      } as any
    ))
  .toBeFalsy()
})

test('works with boolean tuple', () => {
  expect(equals([true, false])([true, false])).toBeTruthy()
  expect(equals([true, false])([true, true])).toBeFalsy()
})

test('works with equal objects within array', () => {
  const objFirst = {
    a: {
      b: 1,
      c: 2,
      d: [1],
    },
  }
  const objSecond = {
    a: {
      b: 1,
      c: 2,
      d: [1],
    },
  }

  const x = [1, 2, objFirst, null, '', []]
  const y = [1, 2, objSecond, null, '', []]
  expect(equals(x)(y)).toBeTruthy()
})

test('works with different objects within array', () => {
  const objFirst = { a: { b: 1 } }
  const objSecond = { a: { b: 2 } }

  const x = [1, 2, objFirst, null, '', []]
  const y = [1, 2, objSecond, null, '', []]
  expect(equals(x)(y)).toBeFalsy()
})

test('works with undefined as second argument', () => {
  expect(equals(1)(undefined as any)).toBeFalsy()
  expect(equals(undefined)(undefined)).toBeTruthy()
})

test('compare sets', () => {
  const toCompareDifferent = new Set([{ a: 1 }, { a: 2 }])
  const toCompareSame = new Set([{ a: 1 }, { a: 2 }, { a: 1 }])
  const testSet = new Set([{ a: 1 }, { a: 2 }, { a: 1 }])
  expect(equals(toCompareSame)(testSet)).toBeTruthy()
  expect(equals(toCompareDifferent)(testSet)).toBeFalsy()
})

test('compare simple sets', () => {
  const testSet = new Set(['2', '3', '3', '2', '1'])
  expect(equals(new Set(['3', '2', '1']))(testSet)).toBeTruthy()
  expect(equals(new Set(['3', '2', '0']))(testSet)).toBeFalsy()
})

test('various examples', () => {
  expect(equals([1, 2, 3])([1, 2, 3])).toBeTruthy()
  expect(equals([1, 2, 3])([1, 2])).toBeFalsy()
  expect(equals({})({})).toBeTruthy()
})

---------------

evolve


evolve<T>(rules: {
	[K in keyof T]?: (x: T[K]) => T[K]
}): (obj: T) => T

It takes object of functions as set of rules. These rules are applied to the iterable input to produce the result. It doesn't support nested rules, i.e rules are only one level deep.

const input = {
	foo: 2,
	baz: 'baz',
}
const result = R.pipe(
	input,
	R.evolve({
		foo: x => x + 1,
	})
)
// => result is { foo: 3, baz: 'baz' }

Try this R.evolve example in Rambda REPL

All TypeScript definitions
evolve<T>(rules: {
	[K in keyof T]?: (x: T[K]) => T[K]
}): (obj: T) => T;
R.evolve source
import { mapObject } from './mapObject.js'
import { type } from './type.js'

export function evolve(rules) {
  return mapObject((x, prop) => type(rules[prop]) === 'Function' ? rules[prop](x): x)
}
Tests
import { evolve } from './evolve'
import { pipe } from './pipe'

test('happy', () => {
  const input = {
    baz: 1,
    foo: 2,
    nested: {
      a: 1,
      bar: 3,
    },
  }
  const result = pipe(input,
    evolve({
      foo: (x) => x + 1,
    })
  )
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expectTypeOf(result.baz).toEqualTypeOf<number>()
  expectTypeOf(result.nested.a).toEqualTypeOf<number>()
  expect(result).toEqual({
    baz: 1,
    foo: 3,
    nested: { a: 1, bar: 3 },
  })
})

---------------

excludes


excludes(list: readonly string[] | string): (substringToFind: string) => boolean

Opposite of R.includes

R.equals is used to determine equality.

const result = [
  R.excludes('foo')('ar'),
  R.excludes([{a: 1}])({a: 2})
]
// => [true, true ]

Try this R.excludes example in Rambda REPL

All TypeScript definitions
excludes(list: readonly string[] | string): (substringToFind: string) => boolean;
excludes<T>(list: readonly T[]): (target: T) => boolean;
R.excludes source
import { includes } from './includes.js'

export function excludes(iterable) {
  return valueToFind => !includes(iterable)(valueToFind)
}
Tests
import { excludes } from './excludes'
import { pipe } from './pipe'

test('happy', () => {
  const list = [{ a: { b: '1' } }, { a: { b: '2' } }, { a: { b: '3' } }]
  const result = pipe({ a: { b: '1' } }, excludes(list))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(false)

  const stringResult = pipe('foo', excludes('bar'))
  expectTypeOf(stringResult).toEqualTypeOf<boolean>()
  expect(stringResult).toBe(true)
})

test('excludes with string', () => {
  const str = 'more is less'
  expect(excludes(str)('less')).toBeFalsy()
  expect(excludes(str)('never')).toBeTruthy()
})

test('excludes with array', () => {
  const arr = [1, 2, 3]
  expect(excludes(arr)(2)).toBeFalsy()
  expect(excludes(arr)(4)).toBeTruthy()
})

---------------

exists


exists<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true if there is at least one element in list that satisfy the predicate.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 'bar'}, {foo: 1}]

const result = R.exists(predicate)(list)
// => true

Try this R.exists example in Rambda REPL

All TypeScript definitions
exists<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.exists source
import { find } from './find.js'

export function exists(predicate) {
  return list => {
		return find(predicate)(list) !== undefined
  }
}
Tests
import { exists } from './exists'
import { propEq } from './propEq'
import { pipe } from './pipe'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }]

test('happy', () => {
  const predicate = (x: number) => x > 2
  const result = pipe([1, 2, 3], exists(predicate))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('nothing is found', () => {
  const fn = propEq(4, 'a')
  expect(exists(fn)(list)).toBe(false)
})

---------------

filter


filter<T, S extends T>(
  predicate: (value: T) => value is S,
): (list: T[]) => S[]

It filters list or object input using a predicate function.

const predicate = x => x > 1
const list = [1, 2, 3]
const result = R.filter(predicate)(list)
// => [2, 3]

Try this R.filter example in Rambda REPL

All TypeScript definitions
filter<T, S extends T>(
  predicate: (value: T) => value is S,
): (list: T[]) => S[];
filter<T>(
	predicate: BooleanConstructor,
): (list: readonly T[]) => ExcludeFalsy<T>[];
filter<T>(
	predicate: BooleanConstructor,
): (list: T[]) => ExcludeFalsy<T>[];
filter<T>(
	predicate: (value: T, index: number) => boolean,
): (list: T[]) => T[];
...
...
R.filter source
export function filter(predicate) {
  return list => {
    if (!list) {
      throw new Error('Incorrect iterable input')
    }
    let index = 0
    const len = list.length
    const willReturn = []

    while (index < len) {
      if (predicate(list[index], index)) {
        willReturn.push(list[index])
      }

      index++
    }

    return willReturn
  }
}
Tests
import { filter } from './filter'
import { includes } from './includes'
import { pipe } from './pipe'
import { reject } from './reject'
import { sort } from './sort'
import { split } from './split'
import { uniq } from './uniq'

test('happy', () => {
  const isEven = (n: number) => n % 2 === 0
  expect(filter(isEven)([1, 2, 3, 4])).toEqual([2, 4])
})

test('using Boolean', () => {
  expect(filter(Boolean)([null, 0, 1, 2])).toEqual([1, 2])
})

test('within pipe', () => {
  const result = pipe(
    [1, 2, 3],
    filter((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([2, 3])
})

test('with index', () => {
  const result = pipe(
    [1, 2, 3],
    filter((x: number, i: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      expectTypeOf(i).toEqualTypeOf<number>()
      return x > 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([2, 3])
})

test('complex example', () => {
  const text = `Dies ist ein einfacher Beispielsatz. Il fait beau aujourd'hui!`
  const language = 'de'
  const SENTENCE_END_CHARS = ['.', '!', '?', '।', '؟']
  const result = pipe(
    text,
    split(''),
    uniq,
    filter((char: string) => {
      if (language === 'de') {
        return /[A-Za-zäßüöÜÖÄ]/g.test(char) === false
      }
      if (language === 'fr') {
        return /[A-Za-zÀÉàâçèéêîïôùû']/g.test(char) === false
      }
      throw new Error(`Language ${language} not supported`)
    }),
    sort((a: string, b: string) => (a === b ? 0 : a > b ? 1 : -1)),
    filter((char: string) => char.trim().length > 0),
    reject(includes(SENTENCE_END_CHARS)),
  )
  expectTypeOf(result).toEqualTypeOf<string[]>()
})

test('narrowing type', () => {
  interface Foo {
    a: number
  }
  interface Bar extends Foo {
    b: string
  }
  type T = Foo | Bar
  const testList: T[] = [{ a: 1 }, { a: 2 }, { a: 3 }]
  const filterBar = (x: T): x is Bar => {
    return typeof (x as Bar).b === 'string'
  }
  const result = pipe(testList, filter(filterBar))
  expectTypeOf(result).toEqualTypeOf<Bar[]>()
})

test('narrowing type - readonly', () => {
  interface Foo {
    a: number
  }
  interface Bar extends Foo {
    b: string
  }
  type T = Foo | Bar
  const testList: T[] = [{ a: 1 }, { a: 2 }, { a: 3 }] as const
  const filterBar = (x: T): x is Bar => {
    return typeof (x as Bar).b === 'string'
  }
  const result = pipe(testList, filter(filterBar))
  expectTypeOf(result).toEqualTypeOf<Bar[]>()
})

test('filtering NonNullable - list of objects', () => {
  const testList = [{ a: 1 }, { a: 2 }, false, { a: 3 }]
  const result = pipe(testList, filter(Boolean))
  expectTypeOf(result).toEqualTypeOf<{ a: number }[]>()
})

test('filtering NonNullable - readonly', () => {
  const testList = [1, 2, true, false, null, undefined, 3] as const
  const result = pipe(testList, filter(Boolean))
  expectTypeOf(result).toEqualTypeOf<(1 | 2 | 3)[]>()
})

---------------

filterAsync


filterAsync<T>(
	predicate: (value: T) => Promise<boolean>,
): (list: T[]) => Promise<T[]>
All TypeScript definitions
filterAsync<T>(
	predicate: (value: T) => Promise<boolean>,
): (list: T[]) => Promise<T[]>;
R.filterAsync source
export function filterAsync(predicate) {
  return async list => {
    const willReturn = []
    let index = 0
    for (const x of list) {
      if (await predicate(x, index)) {
        willReturn.push(list[index])
      }
      index++
    }

    return willReturn
  }
}
Tests
import { filterAsync } from './filterAsync'
import { pipeAsync } from './pipeAsync'

test('happy', async () => {
  const isEven = async (n: number) => n % 2 === 0
  expect(await filterAsync(isEven)([1, 2, 3, 4])).toEqual([2, 4])
})

test('within pipe', async () => {
  const result = await pipeAsync(
    [1, 2, 3],
    filterAsync(async (x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([2, 3])
})

---------------

filterMap


filterMap<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, ExcludeFalsy<U>>

Same as R.map but it filters out null/undefined if returned from functor functions.

💥 This function doesn't work with objects (use R.mapObject instead)

const result = R.pipe(
	[1, 2, 3],
	R.filterMap(x => x > 1 ? x : null)
)
// => [2, 3]

Try this R.filterMap example in Rambda REPL

All TypeScript definitions
filterMap<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, ExcludeFalsy<U>>;
filterMap<T extends IterableContainer, U>(
	fn: (value: T[number]) => U,
): (data: T) => Mapped<T, ExcludeFalsy<U>>;
R.filterMap source
import {mapFn} from './map.js'

export function filterMap(fn) {
  return list => mapFn(fn, list).filter(Boolean)
}
Tests
import { filterMap } from './filterMap'
import { pipe } from './pipe'

const double = (x: number) => x > 1 ? x * 2 : null

test('happy', () => {
  expect(filterMap<number[], number | null>(double)([1, 2, 3])).toEqual([4, 6])
})

test('within pipe', () => {
  const result = pipe(
    [1, 2, 3],
    (x: number[]) => x,
    filterMap((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return Math.random() > 0.5 ? String(x) : null
    }),
    filterMap((x: string) => {
      expectTypeOf(x).toEqualTypeOf<string>()
      return Math.random() > 0.5 ? Number(x) : ''
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
})

---------------

filterObject


filterObject<T extends object>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => boolean,
): <U extends T>(data: T) => U

It loops over each property of obj and returns a new object with only those properties that satisfy the predicate.

const result = R.filterObject(
	(val, prop) => prop === 'a' || val > 1
)({a: 1, b: 2, c:3})
// => {a: 1, c: 3}

Try this R.filterObject example in Rambda REPL

All TypeScript definitions
filterObject<T extends object>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => boolean,
): <U extends T>(data: T) => U;
R.filterObject source
export function filterObject(predicate) {
  return obj => {
    const willReturn = {}

    for (const prop in obj) {
      if (predicate(obj[prop], prop, obj)) {
        willReturn[prop] = obj[prop]
      }
    }

    return willReturn
  }
}
Tests
import { filterObject } from './filterObject'
import { pipe } from './pipe'

test('happy', () => {
  const testInput = { a: 1, b: 2, c: 3 }
  const result = pipe(
    testInput,
    filterObject((x: number, prop: string, obj: typeof testInput) => {
      expect(prop).toBeOneOf(['a', 'b', 'c'])
      expect(obj).toBe(testInput)
      return x > 1
    })
  )
  expect(result).toEqual({ b: 2, c: 3 })
})

test('require explicit type', () => {
  const result = pipe(
    { a: 1, b: 2 },
    filterObject<{ b: number }>((a: number) => {
      expectTypeOf(a).toEqualTypeOf<number>()
      return a > 1
    }),
  )
  expectTypeOf(result.b).toEqualTypeOf<number>()
  expect(result).toEqual({ b: 2 })
})

---------------

find


find<T, S extends T>(predicate: (x: T) => x is S): (list: T[]) => S | undefined

It returns the first element of list that satisfy the predicate.

If there is no such element, it returns undefined.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 'bar'}, {foo: 1}]

const result = R.find(predicate)(list)
// => {foo: 1}

Try this R.find example in Rambda REPL

All TypeScript definitions
find<T, S extends T>(predicate: (x: T) => x is S): (list: T[]) => S | undefined;
find<T>(predicate: (x: T) => boolean): (list: T[]) => T | undefined;
R.find source
export function find(predicate) {
  return list => {
    let index = 0
    const len = list.length

    while (index < len) {
      const x = list[index]
      if (predicate(x)) {
        return x
      }

      index++
    }
  }
}
Tests
import { find } from './find'
import { propEq } from './propEq'
import { pipe } from './pipe'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }]

test('happy', () => {
  const fn = propEq(2, 'a')
  const result = find(fn)(list)
  expectTypeOf(result).toEqualTypeOf<{ a: number } | undefined>()
  expect(result).toEqual({ a: 2 })
})

test('nothing is found', () => {
  const fn = propEq(4, 'a')
  expect(find(fn)(list)).toBeUndefined()
})

test('with empty list', () => {
  expect(find(() => true)([])).toBeUndefined()
})

test('has type guard narrowing', () => {
  const items = ['hello', 'world', 42] as (string | number)[]
  const result = pipe(
    items,
    find((x): x is string => typeof x === 'string'),
  )
  expectTypeOf(result).toEqualTypeOf<string | undefined>()
  expect(result).toBe('hello')
})

---------------

findIndex


findIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number

It returns the index of the first element of list satisfying the predicate function.

If there is no such element, then -1 is returned.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 'bar'}, {foo: 1}]

const result = R.findIndex(predicate)(list)
// => 1

Try this R.findIndex example in Rambda REPL

All TypeScript definitions
findIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
R.findIndex source
export function findIndex(predicate) {
  return list => {
    const len = list.length
    let index = -1

    while (++index < len) {
      if (predicate(list[index])) {
        return index
      }
    }

    return -1
  }
}
Tests
import { findIndex } from './findIndex'
import { propEq } from './propEq'
import { pipe } from './pipe'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }]

test('happy', () => {
  expect(findIndex(propEq(2, 'a'))(list)).toBe(1)
  expect(findIndex(propEq(1, 'a'))(list)).toBe(0)
  expect(findIndex(propEq(4, 'a'))(list)).toBe(-1)
})

test('type test', () => {
  const result = pipe(
    [1, 2, 3],
    findIndex((x: number) => x > 2),
  )
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

---------------

findLast


findLast<T>(fn: (x: T) => boolean): (list: T[]) => T | undefined

It returns the last element of list satisfying the predicate function.

If there is no such element, then undefined is returned.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 0}, {foo: 1}]

const result = R.findLast(predicate)(list)
// => {foo: 1}

Try this R.findLast example in Rambda REPL

All TypeScript definitions
findLast<T>(fn: (x: T) => boolean): (list: T[]) => T | undefined;
R.findLast source
export function findLast(predicate) {
  return list => {
    let index = list.length

    while (--index >= 0) {
      if (predicate(list[index])) {
        return list[index]
      }
    }

    return undefined
  }
}

---------------

findLastIndex


findLastIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number

It returns the index of the last element of list satisfying the predicate function.

If there is no such element, then -1 is returned.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 0}, {foo: 1}]

const result = R.findLastIndex(predicate)(list)
// => 1

Try this R.findLastIndex example in Rambda REPL

All TypeScript definitions
findLastIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
R.findLastIndex source
export function findLastIndex(fn) {
  return list => {
    let index = list.length

    while (--index >= 0) {
      if (fn(list[index])) {
        return index
      }
    }

    return -1
  }
}
Tests
import { findLastIndex } from './findLastIndex'

test('happy', () => {
  const result = findLastIndex((x: number) => x > 1)([1, 1, 1, 2, 3, 4, 1])
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(5)
  expect(findLastIndex((x: number) => x === 0)([0, 1, 1, 2, 3, 4, 1])).toBe(0)
})

---------------

findNth


findNth<T>(predicate: (x: T) => boolean, nth: number): (list: T[]) => T | undefined

It returns the nth element of list that satisfy the predicate function.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 0}, {foo: 1}, {foo: 2}, {foo: 3}]

const result = R.findNth(predicate, 2)(list)
// => {foo: 2}

Try this R.findNth example in Rambda REPL

All TypeScript definitions
findNth<T>(predicate: (x: T) => boolean, nth: number): (list: T[]) => T | undefined;
R.findNth source
export function findNth(predicate, nth) {
  return list => {
    let index = 0
    const len = list.length

    while (index < len) {
      const x = list[index]
      if (predicate(x)) {
				if (nth === 0) return x
				nth--
      }

      index++
    }
  }
}
Tests
import { findNth } from './findNth'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]

test('happy', () => {
  const fn = (x: { a: number }) => x.a > 1
  expect(findNth(fn, 1)(list)).toEqual({ a: 3 })
})

test('nothing is found', () => {
  const fn = (x: { a: number }) => x.a > 4
  expect(findNth(fn, 1)(list)).toBeUndefined()
})

---------------

flatMap


flatMap<T, U extends unknown>(transformFn: (x: T extends any[] ? T[number]: never) => U): (listOfLists: T[]) => U[]

It maps fn over list and then flatten the result by one-level.

const duplicate = n => [ n, n ]
const list = [ 1, 2, 3 ]

const result = R.flatMap(duplicate)(list)
// => [ 1, 1, 2, 2, 3, 3 ]

Try this R.flatMap example in Rambda REPL

All TypeScript definitions
flatMap<T, U extends unknown>(transformFn: (x: T extends any[] ? T[number]: never) => U): (listOfLists: T[]) => U[];
R.flatMap source
export function flatMap(fn) {
  return list => [].concat(...list.map(fn))
}
Tests
import { flatMap } from './flatMap'
import { pipe } from './pipe'

const duplicate = (n: number) => [n, n]

test('happy', () => {
  const listOfLists: string[][] = [
    ['f', 'bar'],
    ['baz', 'b'],
  ]
  const result = pipe(
    listOfLists,
    (x: string[][]) => x,
    flatMap((x: string) => {
      expectTypeOf(x).toEqualTypeOf<string>()
      return Number(x) + 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
})

test('maps then flattens one level', () => {
  expect(flatMap(duplicate)([1, 2, 3])).toEqual([1, 1, 2, 2, 3, 3])
})

test('flattens only one level', () => {
  const nest = (n: number) => [[n]]
  expect(flatMap(nest)([1, 2, 3])).toEqual([[1], [2], [3]])
})

test('can compose', () => {
  function dec(x: number) {
    return [x - 1]
  }
  function times2(x: number) {
    return [x * 2]
  }
  const mdouble = flatMap(times2)
  const mdec = flatMap(dec)
  expect(mdec(mdouble([10, 20, 30]))).toEqual([19, 39, 59])
})

---------------

flatten


flatten<T>(list: any[]): T[]

It deeply flattens an array. You must pass expected output type as a type argument.

const result = R.flatten<number>([
  1,
  2,
  [3, 30, [300]],
  [4]
])
// => [ 1, 2, 3, 30, 300, 4 ]

Try this R.flatten example in Rambda REPL

All TypeScript definitions
flatten<T>(list: any[]): T[];
R.flatten source
import { isArray } from './_internals/isArray.js'

export function flatten(list, input) {
  const willReturn = input === undefined ? [] : input

  for (let i = 0; i < list.length; i++) {
    if (isArray(list[i])) {
      flatten(list[i], willReturn)
    } else {
      willReturn.push(list[i])
    }
  }

  return willReturn
}
Tests
import { flatten } from './flatten'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([1, 2, [3, [4]]], flatten<number>)
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 2, 3, 4])
})

---------------

flattenObject


flattenObject<T extends object>(obj: T): FlattenObject<T>

It transforms object to object where each value is represented with its path.

const result = R.flattenObject(
	[1, 2, 3]
)
// => [3, 1, 2] or [2, 3, 1] or ...

Try this R.flattenObject example in Rambda REPL

All TypeScript definitions
flattenObject<T extends object>(obj: T): FlattenObject<T>;
R.flattenObject source
import { type } from './type.js'

export function flattenObjectHelper(obj, accumulator = []){
  const willReturn = {}
  Object.keys(obj).forEach(key => {
    const typeIs = type(obj[ key ])
    if (typeIs === 'Object'){
      const [ flatResultValue, flatResultPath ] = flattenObjectHelper(obj[ key ],
        [ ...accumulator, key ])
      willReturn[ flatResultPath.join('.') ] = flatResultValue

      return
    } else if (accumulator.length > 0){
      const finalKey = [ ...accumulator, key ].join('.')
      willReturn[ finalKey ] = obj[ key ]

      return
    }
    willReturn[ key ] = obj[ key ]
  })
  if (accumulator.length > 0) return [ willReturn, accumulator ]

  return willReturn
}

export function transformFlatObject(obj){
  const willReturn = {}

  const transformFlatObjectFn = objLocal => {
    const willReturnLocal = {}
    Object.keys(objLocal).forEach(key => {
      const typeIs = type(objLocal[ key ])
      if (typeIs === 'Object'){
        transformFlatObjectFn(objLocal[ key ])

        return
      }
      willReturnLocal[ key ] = objLocal[ key ]
      willReturn[ key ] = objLocal[ key ]
    })

    return willReturnLocal
  }

  Object.keys(obj).forEach(key => {
    const typeIs = type(obj[ key ])
    if (typeIs === 'Object'){
      transformFlatObjectFn(obj[ key ], key)

      return
    }
    willReturn[ key ] = obj[ key ]
  })

  return willReturn
}

export function flattenObject(obj){
  const willReturn = {}

  Object.keys(obj).forEach(key => {
    const typeIs = type(obj[ key ])
    if (typeIs === 'Object'){
      const flatObject = flattenObjectHelper(obj[ key ])
      const transformed = transformFlatObject(flatObject)

      Object.keys(transformed).forEach(keyTransformed => {
        willReturn[ `${ key }.${ keyTransformed }` ] = transformed[ keyTransformed ]
      })
    } else {
      willReturn[ key ] = obj[ key ]
    }
  })

  return willReturn
}
Tests
import { flattenObject } from './flattenObject'

test('happy', () => {
  const result = flattenObject({
    a: 1,
    b: {
      c: 3,
      d: {
        e: 5,
        z: 4,
        f: {
          h: 6,
          i: 7,
          j: {
            k: 8,
            l: 9,
          },
        },
      },
    },
  })
  const expected = {
    'a': 1,
    'b.c': 3,
    'b.d.e': 5,
    'b.d.z': 4,
    'b.d.f.h': 6,
    'b.d.f.i': 7,
    'b.d.f.j.k': 8,
    'b.d.f.j.l': 9,
  }
  expectTypeOf(result['b.c']).toEqualTypeOf<number>()
  expect(result).toEqual(expected)
})

---------------

groupBy


groupBy<T, K extends string = string>(fn: (x: T) => K): (list: T[]) => Partial<Record<K, T[]>>

It splits list according to a provided groupFn function and returns an object.

const list = [ 'a', 'b', 'aa', 'bb' ]
const groupFn = x => x.length

const result = R.groupBy(groupFn)(list)
// => { '1': ['a', 'b'], '2': ['aa', 'bb'] }

Try this R.groupBy example in Rambda REPL

All TypeScript definitions
groupBy<T, K extends string = string>(fn: (x: T) => K): (list: T[]) => Partial<Record<K, T[]>>;
R.groupBy source
export function groupByFallback(groupFn, list) {
    const result = {}
    for (let i = 0; i < list.length; i++) {
      const item = list[i]
      const key = groupFn(item)

      if (!result[key]) {
        result[key] = []
      }

      result[key].push(item)
    }

    return result
}

export function groupBy(groupFn) {
  return iterable => Object.groupBy ? Object.groupBy(iterable,groupFn) : groupByFallback(groupFn, iterable)
}
Tests
import { groupBy } from './groupBy'
import { pipe } from './pipe'

test('happy', () => {
  const groupByFn = (x: string) => String(x.length)
  const list = ['foo', 'bar']
  const result = pipe(list, groupBy(groupByFn))
  expectTypeOf(result).toEqualTypeOf<Partial<Record<string, string[]>>>()
  expect(result).toEqual({ '3': ['foo', 'bar'] })
})

test('with list', () => {
  const inventory = [
    { name: 'asparagus', type: 'vegetables', quantity: 9 },
    { name: 'bananas', type: 'fruit', quantity: 5 },
    { name: 'goat', type: 'meat', quantity: 23 },
    { name: 'cherries', type: 'fruit', quantity: 12 },
    { name: 'fish', type: 'meat', quantity: 22 },
  ]
  const result = groupBy(
    ({ quantity }: { quantity: number }) =>
      quantity < 6 ? 'restock' : 'sufficient'
  )(inventory)
  expect(result.restock).toEqual([
    { name: 'bananas', type: 'fruit', quantity: 5 },
  ])
  expect(result.sufficient![0]).toEqual(
    { name: 'asparagus', type: 'vegetables', quantity: 9 }
  )
})

---------------


head<T>(listOrString: T): T extends string ? string :
	T extends [] ? undefined:
		T extends readonly [infer F, ...infer R] ? F :
			T extends readonly [infer F] ? F :
				T extends [infer F] ? F :
					T extends [infer F, ...infer R] ? F :
						T extends unknown[] ? T[number] :
							undefined

It returns the first element of list or string input. It returns undefined if array has length of 0.

const result = [
  R.head([1, 2, 3]),
  R.head('foo')
]
// => [1, 'f']

Try this R.head example in Rambda REPL

All TypeScript definitions
head<T>(listOrString: T): T extends string ? string :
	T extends [] ? undefined:
		T extends readonly [infer F, ...infer R] ? F :
			T extends readonly [infer F] ? F :
				T extends [infer F] ? F :
					T extends [infer F, ...infer R] ? F :
						T extends unknown[] ? T[number] :
							undefined;
R.head source
export function head(listOrString) {
  if (typeof listOrString === 'string') {
    return listOrString[0] || ''
  }

  return listOrString[0]
}
Tests
import { head } from './head'

test('happy', () => {
  expectTypeOf(head('foo')).toEqualTypeOf<string>()
  expectTypeOf(head('')).toEqualTypeOf<string>()
  expectTypeOf(head([1, 2, 3])).toEqualTypeOf<number>()
  expectTypeOf(head([])).toEqualTypeOf<never>()
  expect(head(['fi', 'fo', 'fum'])).toBe('fi')
	
  expectTypeOf(head([1, 'foo', 3, 'bar'])).toEqualTypeOf<string | number>()
})

---------------

includes


includes<T>(list: readonly T[]): (target: T) => boolean

If input is string, then this method work as native String.includes.

If input is array, then R.equals is used to define if valueToFind belongs to the list.

const result = [
  R.includes('foo')('oo'),
  R.includes([{a: 1}])({a: 1})
]
// => [true, true ]

Try this R.includes example in Rambda REPL

All TypeScript definitions
includes<T>(list: readonly T[]): (target: T) => boolean;
includes(list: readonly string[] | string): (substringToFind: string) => boolean;
R.includes source
import { isArray } from './_internals/isArray.js'
import { _indexOf } from './equals.js'

export function includes(iterable) {
  return valueToFind => {
    if (typeof iterable === 'string') {
      return iterable.includes(valueToFind)
    }
    if (!iterable) {
      throw new TypeError(`Cannot read property \'indexOf\' of ${iterable}`)
    }
    if (!isArray(iterable)) {
      return false
    }

    return _indexOf(valueToFind, iterable) > -1
  }
}
Tests
import { includes } from './includes'
import { pipe } from './pipe'

test('happy', () => {
  const list = [{ a: { b: '1' } }, { a: { b: '2' } }, { a: { b: '3' } }]
  const result = pipe({ a: { b: '1' } }, includes(list))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBeTruthy()
})

test('with string as iterable', () => {
  const str = 'foo bar'
  expect(includes(str)('foo')).toBeTruthy()
  expect(includes(str)('never')).toBeFalsy()
})

test('with array as iterable', () => {
  const arr = [1, 2, 3]
  expect(includes(arr)(2)).toBeTruthy()
  expect(includes(arr)(4)).toBeFalsy()
})

test('with list of objects as iterable', () => {
  const arr = [{ a: 1 }, { b: 2 }, { c: 3 }]
  expect(includes(arr)({ c: 3 })).toBeTruthy()
})

test('with NaN', () => {
  expect(includes([Number.NaN])(Number.NaN)).toBeTruthy()
})

test('with wrong input that does not throw', () => {
  expect(includes([1])(/foo/g as never)).toBeFalsy()
})

---------------

indexBy


indexBy<T, K extends keyof T>(
  property: K
): (list: readonly T[]) => Record<string, T>

It transforms list of objects to object using specified property as the base for the returned object.

const result = R.indexBy(
	'id'
)([{id: 'xyz', title: 'A'}, {id: 'abc', title: 'B'}])
// => {abc: {id: 'abc', title: 'B'}, xyz: {id: 'xyz', title: 'A'}}

Try this R.indexBy example in Rambda REPL

All TypeScript definitions
indexBy<T, K extends keyof T>(
  property: K
): (list: readonly T[]) => Record<string, T>;
indexBy<T, K extends keyof T>(
  property: K
): (list: T[]) => Record<string, T>;
R.indexBy source
export function indexBy(property){
	return list => {
		const toReturn = {}
		for (let i = 0; i < list.length; i++){
			const item = list[ i ]
			const key = item[property]
			if(key !== undefined){
				toReturn[ key ] = item
			}
		}
	
		return toReturn
	}
}
Tests
import { indexBy } from './indexBy'
import { pipe } from './pipe'

test('happy', () => {
  const list = [{ id: 'xyz', title: 'A' }, { id: 'abc', title: 'B' }]
  const result = pipe(list, indexBy('id'))
  expectTypeOf(result.abc).toEqualTypeOf<{ id: string; title: string; }>()
  expect(result).toEqual({ abc: { id: 'abc', title: 'B' }, xyz: { id: 'xyz', title: 'A' } })
})

---------------

indexOf


indexOf<T>(valueToFind: T): (list: T[]) => number

It uses R.equals for list of objects/arrays or native indexOf for any other case.

const result = [
  R.indexOf({a:1})([{a:1}, {a:2}]),
  R.indexOf(2)([1, 2, 3]),
]
// => [0, 1]

Try this R.indexOf example in Rambda REPL

All TypeScript definitions
indexOf<T>(valueToFind: T): (list: T[]) => number;
R.indexOf source
import { _indexOf } from './equals.js'

export function indexOf(valueToFind) {
  return list => _indexOf(valueToFind, list)
}
Tests
import { indexOf } from './indexOf'

test('happy', () => {
  const list = [{ a: 1 }, { a: 2 }]
  const result = indexOf({ a: 1 })(list)
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(0)
})

test('with NaN', () => {
  expect(indexOf(Number.NaN)([Number.NaN])).toBe(0)
})

test('will throw with bad input', () => {
  expect(() => indexOf([])(true as any)).toThrow()
})

test('with numbers', () => {
  expect(indexOf(3)([1, 2, 3, 4])).toBe(2)
  expect(indexOf(10)([1, 2, 3, 4])).toBe(-1)
})

test('list of objects use R.equals', () => {
  const listOfObjects = [{ a: 1 }, { b: 2 }, { c: 3 }]
  expect(indexOf({ c: 4 } as any)(listOfObjects)).toBe(-1)
  expect(indexOf({ c: 3 } as any)(listOfObjects)).toBe(2)
})

test('list of arrays use R.equals', () => {
  const listOfLists = [[1], [2, 3], [2, 3, 4], [2, 3], [1], []]
  expect(indexOf([] as number[])(listOfLists)).toBe(5)
  expect(indexOf([1])(listOfLists)).toBe(0)
  expect(indexOf([2, 3, 4])(listOfLists)).toBe(2)
  expect(indexOf([2, 3, 5])(listOfLists)).toBe(-1)
})

---------------

init


init<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : 
	T extends [...infer U, any] ? U : T : T extends string ? string : never

It returns all but the last element of list or string input.

const result = [
  R.init([1, 2, 3]) ,
  R.init('foo')  // => 'fo'
]
// => [[1, 2], 'fo']

Try this R.init example in Rambda REPL

All TypeScript definitions
init<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : 
	T extends [...infer U, any] ? U : T : T extends string ? string : never;
R.init source
import { baseSlice } from './_internals/baseSlice.js'

export function init(input) {
  if (typeof input === 'string') {
    return input.slice(0, -1)
  }

  return input.length ? baseSlice(input, 0, -1) : []
}
Tests
import { init } from './init'
import { pipe } from './pipe'

test('happy', () => {
  expectTypeOf(init('foo')).toEqualTypeOf<string>()
  expect(init('foo')).toBe('fo')

  const result = pipe(['foo', 'bar', 1, 2, 3], init)
  expectTypeOf(result).toEqualTypeOf<(string | number)[]>()
  expect(result).toEqual(['foo', 'bar', 1, 2])
})

test('with array', () => {
  expect(init([1, 2, 3])).toEqual([1, 2])
  expect(init([1, 2])).toEqual([1])
  expect(init([1])).toEqual([])
  expect(init([])).toEqual([])
})

test('with string', () => {
  expect(init('foo')).toBe('fo')
  expect(init('f')).toBe('')
  expect(init('')).toBe('')
})

---------------

interpolate


interpolate(inputWithTags: string): (templateArguments: object) => string

It generates a new string from inputWithTags by replacing all {{x}} occurrences with values provided by templateArguments.

const inputWithTags = 'foo is {{bar}} even {{a}} more'
const templateArguments = {"bar":"BAR", a: 1}

const result = R.interpolate(inputWithTags)(templateArguments)
const expected = 'foo is BAR even 1 more'
// => `result` is equal to `expected`

Try this R.interpolate example in Rambda REPL

All TypeScript definitions
interpolate(inputWithTags: string): (templateArguments: object) => string;
R.interpolate source
const getOccurrences = input => input.match(/{{\s*.+?\s*}}/g)
const getOccurrenceProp = occurrence => occurrence.replace(/{{\s*|\s*}}/g, '')

const replace = ({ inputHolder, prop, replacer }) => {
  const regexBase = `{{${prop}}}`
  const regex = new RegExp(regexBase, 'g')
  return inputHolder.replace(regex, replacer)
}

export function interpolate(input) {
  return templateInput => {
    const occurrences = getOccurrences(input)
    if (occurrences === null) {
      return input
    }
    let inputHolder = input

    for (const occurrence of occurrences) {
      const prop = getOccurrenceProp(occurrence)
      inputHolder = replace({
        inputHolder,
        prop,
        replacer: templateInput[prop],
      })
    }

    return inputHolder
  }
}
Tests
import { interpolate } from './interpolate'

test('happy', () => {
  const templateInput = 'foo {{x}} baz'
  const templateArguments = { x: 'led zeppelin' }
  const result = interpolate(templateInput)(templateArguments)
  expectTypeOf(result).toEqualTypeOf<string>()
  expect(result).toBe('foo led zeppelin baz')
})

---------------

intersection


intersection<T>(listA: T[]): (listB: T[]) => T[]

It loops through listA and listB and returns the intersection of the two according to R.equals.

const listA = [ { id : 1 }, { id : 2 }, { id : 3 }, { id : 4 } ]
const listB = [ { id : 3 }, { id : 4 }, { id : 5 }, { id : 6 } ]

const result = R.intersection(listA)(listB)
// => [{ id : 3 }, { id : 4 }]

Try this R.intersection example in Rambda REPL

All TypeScript definitions
intersection<T>(listA: T[]): (listB: T[]) => T[];
R.intersection source
import { filter } from './filter.js'
import { includes } from './includes.js'

export function intersection(listA) {
  return listB => filter(includes(listA))(listB)
}
Tests
import { intersection } from './intersection'

test('happy', () => {
  const list1 = [1, 2, 3]
  const list2 = [1, 3, 5]
  const result = intersection(list1)(list2)
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 3])
})

test('intersection', () => {
  const list1 = [1, 2, 3, 4]
  const list2 = [3, 4, 5, 6]
  expect(intersection(list1)(list2)).toEqual([3, 4])
  expect(intersection([])([])).toEqual([])
})

test('intersection with objects', () => {
  const list1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
  const list2 = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]
  expect(intersection(list1)(list2)).toEqual([{ id: 3 }, { id: 4 }])
})

test('order is the same as in Ramda', () => {
  const list = ['a', 'b', 'c', 'd']
  expect(intersection(list)(['b', 'c'])).toEqual(['b', 'c'])
  expect(intersection(list)(['c', 'b'])).toEqual(['c', 'b'])
})

---------------

intersectionWith


intersectionWith<T1, T2>(
  pred: (a: T1, b: T2) => boolean,
  list1: T1[],
): (list2: T2[]) => T1[]

It returns a new list by applying a predicate function to all elements of list1 and list2 and keeping only these elements where predicate returns true.

const list1 = [1, 2, 3, 4, 5]
const list2 = [4, 5, 6]
const predicate = (x, y) => x >= y
const result = R.intersectionWith(predicate, list1)(list2)
// => [4, 5]

Try this R.intersectionWith example in Rambda REPL

All TypeScript definitions
intersectionWith<T1, T2>(
  pred: (a: T1, b: T2) => boolean,
  list1: T1[],
): (list2: T2[]) => T1[];
R.intersectionWith source
function _includesWith(pred, x, list) {
  let idx = 0
  const len = list.length

  while (idx < len) {
    if (pred(x, list[idx])) {
      return true
    }

    idx += 1
  }

  return false
}
function _filter(fn, list) {
  let idx = 0
  const len = list.length
  const result = []

  while (idx < len) {
    if (fn(list[idx])) {
      result[result.length] = list[idx]
    }

    idx += 1
  }

  return result
}

export function intersectionWith(pred, xs) {
  return ys => _filter(x => _includesWith(pred, x, ys), xs)
}
Tests
import { intersectionWith } from './intersectionWith'
import { pipe } from './pipe'

test('happy', () => {
  const list1 = [1, 2, 3]
  const list2 = [1, 3, 5]
  const result = pipe(list1, intersectionWith((x, y) => x === y, list2))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 3])
})

---------------

intersperse


intersperse<T>(separator: T): (list: T[]) => T[]

It adds a separator between members of list.

const list = [ 0, 1, 2, 3 ]
const separator = 10
const result = R.intersperse(separator)(list)
// => [0, 10, 1, 10, 2, 10, 3]

Try this R.intersperse example in Rambda REPL

All TypeScript definitions
intersperse<T>(separator: T): (list: T[]) => T[];
R.intersperse source
export function intersperse(separator) {
  return list => {
    let index = -1
    const len = list.length
    const willReturn = []

    while (++index < len) {
      if (index === len - 1) {
        willReturn.push(list[index])
      } else {
        willReturn.push(list[index], separator)
      }
    }

    return willReturn
  }
}
Tests
import { intersperse } from './intersperse'

test('happy', () => {
  const result = intersperse('|')(['foo', 'bar'])
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['foo', '|', 'bar'])
})

---------------

join


join<T>(glue: string): (list: T[]) => string

It returns a string of all list instances joined with a glue.

R.join('-')([1, 2, 3])  // => '1-2-3'

Try this R.join example in Rambda REPL

All TypeScript definitions
join<T>(glue: string): (list: T[]) => string;
R.join source
export function join(glue) {
  return list => list.join(glue)
}
Tests
import { join } from './join'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([1, 2, 3], join('|'))
  expectTypeOf(result).toEqualTypeOf<string>()
  expect(result).toBe('1|2|3')
})

---------------

last


last<T>(listOrString: T): T extends string ? string :
  T extends [] ? undefined :
    T extends readonly [...infer R, infer L] ? L :
      T extends readonly [infer L] ? L :
        T extends [infer L] ? L :
          T extends [...infer R, infer L] ? L :
            T extends unknown[] ? T[number] :
              undefined

It returns the last element of input, as the input can be either a string or an array. It returns undefined if array has length of 0.

const result = [
  R.last([1, 2, 3]),
  R.last('foo'),
]
// => [3, 'o']

Try this R.last example in Rambda REPL

All TypeScript definitions
last<T>(listOrString: T): T extends string ? string :
  T extends [] ? undefined :
    T extends readonly [...infer R, infer L] ? L :
      T extends readonly [infer L] ? L :
        T extends [infer L] ? L :
          T extends [...infer R, infer L] ? L :
            T extends unknown[] ? T[number] :
              undefined;
R.last source
export function last(listOrString) {
  if (typeof listOrString === 'string') {
    return listOrString[listOrString.length - 1] || ''
  }

  return listOrString[listOrString.length - 1]
}
Tests
import { last } from './last'

test('happy', () => {
  expectTypeOf(last([1, 2, 3])).toEqualTypeOf<number>()
  expectTypeOf(last([])).toEqualTypeOf<never>()
  expectTypeOf(last('abc')).toEqualTypeOf<string>()
  expect(last([1, 2, 3])).toBe(3)
})

test('with list', () => {
  expect(last([1, 2, 3])).toBe(3)
  expect(last([])).toBeUndefined()
})

test('with string', () => {
  expect(last('abc')).toBe('c')
  expect(last('')).toBe('')
})

---------------

lastIndexOf


lastIndexOf<T>(target: T): (list: T[]) => number

It returns the last index of target in list array.

R.equals is used to determine equality between target and members of list.

If there is no such index, then -1 is returned.

const list = [1, 2, 3, 1, 2, 3]
const result = [
  R.lastIndexOf(2)(list),
  R.lastIndexOf(4)(list),
]
// => [4, -1]

Try this R.lastIndexOf example in Rambda REPL

All TypeScript definitions
lastIndexOf<T>(target: T): (list: T[]) => number;
R.lastIndexOf source
import { _lastIndexOf } from './equals.js'

export function lastIndexOf(valueToFind) {
  return list => _lastIndexOf(valueToFind, list)
}
Tests
import { lastIndexOf } from './lastIndexOf'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([{ a: 1 }, { a: 2 }, { a: 3 }], lastIndexOf({ a: 2 }))
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(1)
})

test('with NaN', () => {
  expect(lastIndexOf(Number.NaN)([Number.NaN])).toBe(0)
})

test('without list of objects - no R.equals', () => {
  expect(lastIndexOf(3)([1, 2, 3, 4])).toBe(2)
  expect(lastIndexOf(10)([1, 2, 3, 4])).toBe(-1)
})

test('list of objects uses R.equals', () => {
  const listOfObjects = [{ a: 1 }, { b: 2 }, { c: 3 }]
  expect(lastIndexOf({ c: 4 } as any)(listOfObjects)).toBe(-1)
  expect(lastIndexOf({ c: 3 } as any)(listOfObjects)).toBe(2)
})

test('list of arrays uses R.equals', () => {
  const listOfLists = [[1], [2, 3], [2, 3, 4], [2, 3], [1], []]
  expect(lastIndexOf([] as number[])(listOfLists)).toBe(5)
  expect(lastIndexOf([1])(listOfLists)).toBe(4)
  expect(lastIndexOf([2, 3, 4])(listOfLists)).toBe(2)
  expect(lastIndexOf([2, 3, 5])(listOfLists)).toBe(-1)
})

---------------

map


map<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, U>

It returns the result of looping through iterable with fn.

💥 This function doesn't work with objects (use R.mapObject instead)

const result = R.pipe(
	[1, 2],
	R.map(x => x * 2)
)
// => [2, 4]

Try this R.map example in Rambda REPL

All TypeScript definitions
map<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, U>;
map<T extends IterableContainer, U>(
	fn: (value: T[number]) => U,
): (data: T) => Mapped<T, U>;
R.map source
export function mapFn(
	fn, list
){
	let index = 0
	const willReturn = Array(list.length)
	while (index < list.length) {
		willReturn[index] = fn(list[index], index)
		index++
	}
	return willReturn
}

export function map(fn) {
  return list => mapFn(fn, list)
}
Tests
import { map } from './map'
import { pipe } from './pipe'

const double = (x: number) => x * 2

test('happy', () => {
  expect(map<number[], number>(double)([1, 2, 3])).toEqual([2, 4, 6])
})

test('type test', () => {
  const list = [1, 2, 3]
  const result = pipe(list, x => x, map(x => String(x)))
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['1', '2', '3'])
})

---------------

mapAsync


mapAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
): (data: T) => Promise<Mapped<T, U>>

Sequential asynchronous mapping with fn over members of list.

async function fn(x){
  await R.delay(1000)

  return x+1
}

const result = await R.mapAsync(fn)([1, 2, 3])
// `result` resolves after 3 seconds to `[2, 3, 4]`

Try this R.mapAsync example in Rambda REPL

All TypeScript definitions
mapAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
): (data: T) => Promise<Mapped<T, U>>;
mapAsync<T extends IterableContainer, U>(
  fn: (value: T[number]) => Promise<U>,
): (data: T) => Promise<Mapped<T, U>>;
R.mapAsync source
export function mapAsync(fn) {
  return async list => {
    const willReturn = []
    let i = 0
    for (const x of list) {
      willReturn.push(await fn(x, i++))
    }

    return willReturn
  }
}
Tests
import { delay } from './delay'
import { map } from './map'
import { mapAsync } from './mapAsync'
import { pipeAsync } from './pipeAsync'

const rejectDelay = (a: number) =>
  new Promise((_, reject) => {
    setTimeout(() => {
      reject(a + 20)
    }, 100)
  })

test('happy', async () => {
  const indexes: number[] = []
  const fn = async (x: number, prop: number) => {
    await delay(100)
    indexes.push(prop)
    return x + 1
  }
  const result = await mapAsync<number[], number>(fn)([1, 2, 3])
  expect(result).toEqual([2, 3, 4])
  expect(indexes).toEqual([0, 1, 2])
})

test('with R.pipeAsync', async () => {
  const fn = async (x: number) => x + 1
  const result = await pipeAsync(
    [1, 2, 3],
    map(x => x + 1),
    mapAsync(async x => {
      await delay(x)
      return x
    }),
    mapAsync(fn),
    map(x => x * 10),
  )
  expect(result).toEqual([30, 40, 50])
})

test('error', async () => {
  try {
    await mapAsync<number[], unknown>(rejectDelay)([1, 2, 3])
  } catch (err) {
    expect(err).toBe(21)
  }
})

test('happy', async () => {
  const list = ['a', 'bc', 'def']
  const result = await pipeAsync(
    list,
    mapAsync(async x => {
      await delay(100)
      return x.length % 2 ? x.length + 1 : x.length + 10
    }),
    x => x,
    map(x => x + 1),
    mapAsync(async x => {
      await delay(100)
      return x + 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([4, 14, 6])
})

---------------

mapChain


mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number], index: number) => U,
	fn2: (value: U, index: number) => V,
): (data: T) => Mapped<T, V>

Chained 2 or 3 R.map transformations as one.

const result = R.pipe(
	[1, 2],
	R.mapChain(
		x => x * 2,
		x => [x, x > 3],
	)
)
// => [[2, false], [4, true]]

Try this R.mapChain example in Rambda REPL

All TypeScript definitions
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number], index: number) => U,
	fn2: (value: U, index: number) => V,
): (data: T) => Mapped<T, V>;
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number], index: number) => U,
	fn2: (value: U) => V,
): (data: T) => Mapped<T, V>;
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number]) => U,
	fn2: (value: U, index: number) => V,
): (data: T) => Mapped<T, V>;
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number]) => U,
	fn2: (value: U) => V,
): (data: T) => Mapped<T, V>;
...
...
R.mapChain source
import { mapFn } from './map.js';

export function mapChain(...fns) {
  return list => {
		let result = list.slice()
		fns.forEach((fn) => {
			result = mapFn(fn, result)
		})
		return result
	}
}
Tests
import { mapChain } from './mapChain'
import { pipe } from './pipe'

const double = (x: number) => x * 2

test('happy', () => {
  expect(mapChain<number[], number, number, number>(double, double, double)([1, 2, 3])).toEqual([8, 16, 24])
})

test('type test', () => {
  const list = [1, 2, 3]
  const result = pipe(
    list,
    mapChain(
      x => String(x),
      x => x !== 'foo',
    ),
  )
  expectTypeOf(result).toEqualTypeOf<boolean[]>()
  expect(result).toEqual([true, true, true])
})

---------------

mapKeys


mapKeys<T>(fn: (prop: string, value: T) => string): (obj: Record<string, T>) => Record<string, T>

It returns a copy of obj with keys transformed by fn.

const result = R.mapKeys(
	(key, value) => key.toUpperCase()+value
	)(
	{ a: 1, b: 2 }
)
// => { A1: 1, B2: 2 }

Try this R.mapKeys example in Rambda REPL

All TypeScript definitions
mapKeys<T>(fn: (prop: string, value: T) => string): (obj: Record<string, T>) => Record<string, T>;
R.mapKeys source
export function mapKeys(fn) {
  return obj => {
		const willReturn = {}

		Object.keys(obj).forEach(key => {
			willReturn[fn(key, obj[key])] = obj[key]
		})

		return willReturn
	}
}
Tests
import { mapKeys } from './mapKeys'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(
    { a: 1, b: 2 },
    mapKeys((prop, x) => `${prop}-${x}`),
    mapKeys(prop => `${prop}-${prop}`),
  )
  expectTypeOf(result).toEqualTypeOf<Record<string, number>>()
  expect(result).toEqual({ 'a-1-a-1': 1, 'b-2-b-2': 2 })
})

---------------

mapObject


mapObject<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Value,
): (data: T) => MappedValues<T, Value>
const fn = (val, prop) => `${prop}-${val}`
const obj = {a: 1, b: 2}

const result = R.mapObject(fn)(obj)
// => {a: 'a-1', b: 'b-2'}

Try this R.mapObject example in Rambda REPL

All TypeScript definitions
mapObject<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Value,
): (data: T) => MappedValues<T, Value>;
R.mapObject source
import { keys } from './_internals/keys.js'

export function mapObject(fn) {
  return obj => {
    let index = 0
    const objKeys = keys(obj)
    const len = objKeys.length
    const willReturn = {}

    while (index < len) {
      const key = objKeys[index]
      willReturn[key] = fn(obj[key], key, obj)
      index++
    }

    return willReturn
  }
}
Tests
import { mapObject } from './mapObject'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1 }, mapObject(a => `${a}`))
  expectTypeOf(result).toEqualTypeOf<{ a: string }>()
  expect(result).toEqual({ a: '1' })
})

---------------

mapObjectAsync


mapObjectAsync<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Promise<Value>,
): (data: T) => Promise<MappedValues<T, Value>>
All TypeScript definitions
mapObjectAsync<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Promise<Value>,
): (data: T) => Promise<MappedValues<T, Value>>;
R.mapObjectAsync source
export function mapObjectAsync(fn) {
  return async obj => {
    const willReturn = {}
    for (const prop in obj) {
      willReturn[prop] = await fn(obj[prop], prop)
    }

    return willReturn
  }
}
Tests
import { delay } from './delay'
import { mapObjectAsync } from './mapObjectAsync'
import { pipeAsync } from './pipeAsync'

test('happy', async () => {
  const indexes: string[] = []
  const result = await pipeAsync(
    { a: 1, b: 2 },
    mapObjectAsync(async (x, i) => {
      await delay(100)
      indexes.push(i)
      return x + 1
    }),
  )
  expect(indexes).toEqual(['a', 'b'])
  expect(result).toEqual({ a: 2, b: 3 })
	expectTypeOf(result).toEqualTypeOf<{ a: number; b: number }>()
})

---------------

mapParallelAsync


mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
	batchSize?: number,
): (data: T) => Promise<Mapped<T, U>>

Wrapper around Promise.all for asynchronous mapping with fn over members of list. There is optional batchSize parameter to allow parallel execution to run in batches. In this case, the whole batch must complete before the next batch starts.

All TypeScript definitions
mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
	batchSize?: number,
): (data: T) => Promise<Mapped<T, U>>;
mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[number]) => Promise<U>,
	batchSize?: number,
): (data: T) => Promise<Mapped<T, U>>;
R.mapParallelAsync source
export function mapParallelAsync(fn, batchSize){
	if(!batchSize) return async list =>  Promise.all(list.map(fn))

	return async list => {
		const result = []
		for(let i = 0; i < list.length; i += batchSize){
			const batch = list.slice(i, i + batchSize)
			const batchResult = await Promise.all(batch.map((x, j) => fn(x, i + j)))
			result.push(...batchResult)
		}
		return result
	}	
}
Tests
import { delay } from './delay'
import { mapParallelAsync } from './mapParallelAsync'

test('happy', async () => {
  const fn = async (x: number, i: number) => {
    await delay(100)
    return x + i
  }
  const result = await mapParallelAsync<number[], number>(fn)([1, 2, 3])
	expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 3, 5])
})

test('with batchSize', async () => {
  const fn = async (x: number, i: number) => {
    await delay(100)
    return `${x}:${i}`
  }
  const result = await mapParallelAsync<number[], string>(fn, 2)([1, 2, 3, 4, 5])
  expect(result).toEqual(['1:0', '2:1', '3:2', '4:3', '5:4'])
})

---------------

mapPropObject


mapPropObject<T extends object, K extends keyof T, Value extends unknown>(
	prop: K,
	valueMapper: (
		listItem: T[K] extends ReadonlyArray<infer ElementType> ? ElementType : never,
		list: T[K] extends ReadonlyArray<any> ? T[K] : never,
	) => Value,
): (data: T) => T[K] extends ReadonlyArray<any>
	? MergeTypes<Omit<T, K> & { [P in K]: Value[] }>
	: never

Convenience method, when one needs to maps over a object property that is a list.

const result = pipe(
	{ a: [1,2,3], b: 'foo' },
	mapPropObject('a',x => {
		return {
			a: x,
			flag: x > 2,
		}
	}),
)
// => { a: [{ a: 1, flag: false },{ a: 2, flag: false }, { a: 3, flag: true }], b: 'foo' }

Try this R.mapPropObject example in Rambda REPL

All TypeScript definitions
mapPropObject<T extends object, K extends keyof T, Value extends unknown>(
	prop: K,
	valueMapper: (
		listItem: T[K] extends ReadonlyArray<infer ElementType> ? ElementType : never,
		list: T[K] extends ReadonlyArray<any> ? T[K] : never,
	) => Value,
): (data: T) => T[K] extends ReadonlyArray<any>
	? MergeTypes<Omit<T, K> & { [P in K]: Value[] }>
	: never;
mapPropObject<T extends object, K extends keyof T, Value extends unknown>(
	prop: K,
  valueMapper: (
    listItem: T[K] extends ReadonlyArray<infer ElementType> ? ElementType : never,
  ) => Value,
): (data: T) => T[K] extends ReadonlyArray<any>
  ? MergeTypes<Omit<T, K> & { [P in K]: Value[] }>
  : never;
R.mapPropObject source
export function mapPropObject(fn, prop) {
  return obj => {
		if (!Array.isArray(obj[prop])) return obj
			
			return {
				...obj,
				[prop]: obj[prop].map(fn)
			}
		}
 	 }
Tests
import { mapPropObject } from './mapPropObject'
import { pipe } from './pipe'

const fn = (x: number) => ({ a: x, flag: x > 2 })

test('happy', () => {
  const result = (pipe as any)(
    { a: [1, 2, 3], b: 'foo' },
    (mapPropObject as any)(fn, 'a'),
  )
  expect(result).toEqual({
    a: [
      { a: 1, flag: false },
      { a: 2, flag: false },
      { a: 3, flag: true },
    ],
    b: 'foo',
  })
})

test('type test', () => {
  const result = pipe(
    { a: [1, 2, 3], b: 'foo' },
    mapPropObject('a', fn),
  )
  expectTypeOf(result.a).toEqualTypeOf<{ a: number; flag: boolean }[]>()
  expectTypeOf(result.b).toEqualTypeOf<string>()
})

---------------

match


match(regExpression: RegExp | string): (str: string) => string[]

Curried version of String.prototype.match which returns empty array, when there is no match.

const result = [
  R.match('a')('foo'),
  R.match(/([a-z]a)/g)('bananas')
]
// => [[], ['ba', 'na', 'na']]

Try this R.match example in Rambda REPL

All TypeScript definitions
match(regExpression: RegExp | string): (str: string) => string[];
R.match source
export function match(pattern) {
  return input => {
    const willReturn = input.match(pattern)

    return willReturn === null ? [] : willReturn
  }
}
Tests
import { match } from './match'

test('happy', () => {
  const str = 'foo bar'
  const result = match(/foo/)(str)
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toContain('foo')
})

test('fallback', () => {
  expect(match(/a./g)('foo')).toEqual([])
})

test('with string', () => {
  expect(match('a')('foo')).toEqual([])
})

---------------

maxBy


maxBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T

It returns the greater value between x and y according to compareFn function.

const compareFn = Math.abs

R.maxBy(compareFn, 5)(-7) // => -7

Try this R.maxBy example in Rambda REPL

All TypeScript definitions
maxBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T;
R.maxBy source
export function maxBy(compareFn, x) {
  return y => (compareFn(y) > compareFn(x) ? y : x)
}
Tests
import { maxBy } from './maxBy'
import { pipe } from './pipe'

test('happy', () => {
  const first = 1
  const second = 2
  const result = pipe(second, maxBy(x => (x % 2 === 0 ? 1 : -1), first))
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

---------------

merge


merge<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>

It creates a copy of source object with overwritten newProps properties.

All TypeScript definitions
merge<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>;
R.merge source
export function merge(target) {
  return objectWithNewProps =>
    Object.assign({}, target || {}, objectWithNewProps || {})
}
Tests
import { merge } from './merge'
import { mergeTypes } from './mergeTypes'
import { pipe } from './pipe'

test('happy', () => {
  const obj = { foo: 1, bar: 2 }
  expect(merge(obj)({ bar: 20 })).toEqual({ foo: 1, bar: 20 })
})

test('type test', () => {
  const result = pipe({ foo: 1 }, merge({ bar: 2 }), mergeTypes)
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expectTypeOf(result.bar).toEqualTypeOf<number>()
  expect(result).toEqual({ foo: 1, bar: 2 })
})

---------------

mergeDeep


mergeDeep<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>
All TypeScript definitions
mergeDeep<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>;
R.mergeDeep source
import { type } from './type.js'

const isObject = (x) => type(x) === 'Object'

function mergeDeepFn(source, objectWithNewProps) {
  return [source, objectWithNewProps].reduce((prev, obj) => {
    Object.keys(obj).forEach((key) => {
      const pVal = prev[key]
      const oVal = obj[key]

      if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeepFn(pVal, oVal)
      } else {
        prev[key] = oVal
      }
    })

    return prev
  }, {})
}

export function mergeDeep(source) {
  return (objectWithNewProps) => mergeDeepFn(source, objectWithNewProps)
}
Tests
import { mergeDeep } from './mergeDeep'

test('happy', () => {
  const source = { a: 1, b: [1, 2], c: { d: 1, f: 2, e: [1, 2], h: [1, 2] } }
  const objectWithNewProps = { b: [3], c: { f: 3, s: 3, e: [3] }, q: 3 }
  expect(mergeDeep(source)(objectWithNewProps)).toEqual({
    a: 1, b: [3], c: { d: 1, f: 3, e: [3], h: [1, 2], s: 3 }, q: 3,
  })
})

test('type test', () => {
  const result = mergeDeep({ a: 1 })({ b: 2 })
  expectTypeOf(result).toEqualTypeOf<{ a: number; b: number }>()
  expect(result).toEqual({ a: 1, b: 2 })
})

---------------

mergeTypes


mergeTypes<T>(x: T): MergeTypes<T>

Helper to merge all calculated TypeScript definitions into one definition. It returns its input and it is intended to be used as last method inside R.pipe chain.

All TypeScript definitions
mergeTypes<T>(x: T): MergeTypes<T>;
R.mergeTypes source
export function mergeTypes(x) {
  return x
}

---------------

middle


middle<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : 
	T extends [any, ...infer U, any] ? U : T : T extends string ? string : never

It returns all but the first and last element of input.

const result = [
  R.middle([1, 2, 3, 4]),
  R.middle('bar')
]
// => [[2, 3], 'a']

Try this R.middle example in Rambda REPL

All TypeScript definitions
middle<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : 
	T extends [any, ...infer U, any] ? U : T : T extends string ? string : never;
R.middle source
import { init } from './init.js'
import { tail } from './tail.js'

export function middle(listOrString) {
  return tail(init(listOrString))
}
Tests
import { middle } from './middle'

test('happy', () => {
  expectTypeOf(middle('foo')).toEqualTypeOf<string>()
  expectTypeOf(middle('')).toEqualTypeOf<string>()
  expectTypeOf(middle(['foo', 'bar', 1, 2, 3])).toEqualTypeOf<(string | number)[]>()
  expect(middle('abc')).toBe('b')
})

---------------

minBy


minBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T

It returns the lesser value between x and y according to compareFn function.

const compareFn = Math.abs

R.minBy(compareFn, -5)(2) // => -5

Try this R.minBy example in Rambda REPL

All TypeScript definitions
minBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T;
R.minBy source
export function minBy(compareFn, x) {
  return y => (compareFn(y) < compareFn(x) ? y : x)
}
Tests
import { minBy } from './minBy'

test('happy', () => {
  const result = minBy<number>(Math.abs, -5)(2)
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

---------------

modifyItemAtIndex


modifyItemAtIndex<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[]

It replaces index in array list with the result of replaceFn(list[i]).

const result = R.pipe(
	[1, 2, 3],
	R.modifyItemAtIndex(1, x => x + 1)
) // => [1, 3, 3]

Try this R.modifyItemAtIndex example in Rambda REPL

All TypeScript definitions
modifyItemAtIndex<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
R.modifyItemAtIndex source
import { cloneList } from './_internals/cloneList.js'

export function modifyItemAtIndex(index, replaceFn) {
  return list => {
    const actualIndex = index < 0 ? list.length + index : index
    if (index >= list.length || actualIndex < 0) {
      return list
    }

    const clone = cloneList(list)
    clone[actualIndex] = replaceFn(clone[actualIndex])

    return clone
  }
}
Tests
import { modifyItemAtIndex } from './modifyItemAtIndex'

const add10 = (x: number) => x + 10

test('happy', () => {
  const result = modifyItemAtIndex(1, add10)([0, 1, 2])
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([0, 11, 2])
})

test('with negative index', () => {
  expect(modifyItemAtIndex(-2, add10)([0, 1, 2])).toEqual([0, 11, 2])
})

test('when index is out of bounds', () => {
  expect(modifyItemAtIndex(4, add10)([0, 1, 2, 3])).toEqual([0, 1, 2, 3])
  expect(modifyItemAtIndex(-5, add10)([0, 1, 2, 3])).toEqual([0, 1, 2, 3])
})

---------------

modifyPath


modifyPath<U, T>(path: [], fn: (value: U) => T): (obj: U) => T

It changes a property of object on the base of provided path and transformer function.

const result = R.modifyPath('a.b.c', x=> x+1)({a:{b: {c:1}}})
// => {a:{b: {c:2}}}

Try this R.modifyPath example in Rambda REPL

All TypeScript definitions
modifyPath<U, T>(path: [], fn: (value: U) => T): (obj: U) => T;
modifyPath<
  K0 extends keyof U,
  U,
  T
>(path: [K0], fn: (value: U[K0]) => T): (obj: U) => DeepModify<[K0], U, T>;
modifyPath<
  K0 extends string & keyof U,
  U,
  T
>(path: `${K0}`, fn: (value: U[K0]) => T): (obj: U) => DeepModify<[K0], U, T>;
modifyPath<
  K0 extends keyof U,
  K1 extends keyof U[K0],
  U,
  T
>(path: [K0, K1], fn: (value: U[K0][K1]) => T): (obj: U) => DeepModify<[K0, K1], U, T>;
...
...
R.modifyPath source
import { createPath } from './_internals/createPath.js'
import { path as pathModule } from './path.js'

function assoc(prop, newValue) {
  return obj => Object.assign({}, obj, { [prop]: newValue })
}

function modifyPathFn(pathInput, fn, obj) {
  const path = createPath(pathInput)
  if (path.length === 1) {
    return {
      ...obj,
      [path[0]]: fn(obj[path[0]]),
    }
  }
  if (pathModule(path)(obj) === undefined) {
    return obj
  }

  const val = modifyPathFn(Array.prototype.slice.call(path, 1), fn, obj[path[0]])
  if (val === obj[path[0]]) {
    return obj
  }

  return assoc(path[0], val)(obj)
}

export function modifyPath(pathInput, fn) {
  return obj => modifyPathFn(pathInput, fn, obj)
}
Tests
import { modifyPath } from './modifyPath'
import { pipe } from './pipe'

const obj = { a: { b: { c: 1 } } }

test('happy', () => {
  const result = pipe(obj, modifyPath(['a', 'b', 'c'], (x: number) => String(x)))
  expectTypeOf(result.a.b.c).toEqualTypeOf<string>()
  expect(result).toEqual({ a: { b: { c: '1' } } })
})

---------------

modifyProp


modifyProp<T, K extends keyof T>(
  prop: K,
  fn: (x: T[K]) => T[K],
): (target: T) => T

It changes a property with the result of transformer function.

const person = {
  name : 'foo',
  age  : 20,
}
const result = R.modifyProp('age', x => x + 1)(person)
// => {name: 'foo', age: 21}

Try this R.modifyProp example in Rambda REPL

All TypeScript definitions
modifyProp<T, K extends keyof T>(
  prop: K,
  fn: (x: T[K]) => T[K],
): (target: T) => T;
R.modifyProp source
import { isArray } from './_internals/isArray.js'
import { update } from './update.js'

function modifyFn(property, fn, list) {
  if (list[property] === undefined) {
    return list
  }
  if (isArray(list)) {
    return update(property, fn(list[property]))(list)
  }

  return {
    ...list,
    [property]: fn(list[property]),
  }
}

export function modifyProp(property, fn) {
  return obj => modifyFn(property, fn, obj)
}
Tests
import { modifyProp } from './modifyProp'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 2, c: { d: 3 } }, modifyProp('a', val => val + 1))
  expectTypeOf(result).toEqualTypeOf<{ a: number; b: number; c: { d: number } }>()
  expect(result).toEqual({ a: 2, b: 2, c: { d: 3 } })
})

test('property is missing', () => {
	const person = { name: 'foo', age: 20 }
	
	expect(modifyProp<typeof person, 'age'>('foo' as 'age', (x: number) => x + 1)(person)).toEqual(person)
})

test('adjust if `array` at the given key', () => {
  expect(modifyProp<number[], 1>(1, (x: number) => x + 1)([100, 1400])).toEqual([100, 1401])
})

---------------

none


none<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true, if all members of array list returns false, when applied as argument to predicate function.

const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > 6

const result = R.none(predicate)(arr)
// => true

Try this R.none example in Rambda REPL

All TypeScript definitions
none<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.none source
export function none(predicate) {
  return list => {
    for (let i = 0; i < list.length; i++) {
      if (predicate(list[i])) {
        return false
      }
    }

    return true
  }
}
Tests
import { none } from './none'
import { pipe } from './pipe'

const isEven = (n: number) => n % 2 === 0

test('happy', () => {
  const result = pipe([1, 2, 3], none(x => x > 0))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBeFalsy()
})

test('when true', () => {
  expect(none(isEven)([1, 3, 5, 7])).toBeTruthy()
})

---------------

objectIncludes


objectIncludes<T>(specification: T): (obj: Partial<T>) => boolean

It will return true if specification object fully or partially include obj object.

R.equals is used to determine equality.

const specification = { a : { b : 1 } }
const input = {
  a : { b : 1 },
  c : 2
}

const result = objectIncludes(specification)(input)
// => true

Try this R.objectIncludes example in Rambda REPL

All TypeScript definitions
objectIncludes<T>(specification: T): (obj: Partial<T>) => boolean;
R.objectIncludes source
import { equals } from './equals.js'
import { filterObject } from './filterObject.js'

export function objectIncludes(condition) {
  return obj => {
    const result = filterObject((conditionValue, conditionProp) =>
      equals(conditionValue)(obj[conditionProp]),
    )(condition)

    return Object.keys(result).length === Object.keys(condition).length
  }
}
Tests
import { objectIncludes } from './objectIncludes'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 2, c: { d: 3 } }, objectIncludes({ a: 2 }))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBeFalsy()
})

test('when true', () => {
  const condition = { a: 1 }
  const input = { a: 1, b: 2 }
  expect(objectIncludes(condition)(input)).toBeTruthy()
})

test('when false', () => {
  const condition = { a: 1 }
  const input = { b: 2 }
  expect(objectIncludes(condition as Record<string, number>)(input)).toBeFalsy()
})

test('with nested object', () => {
  const condition = { a: { b: 1 } }
  const input = { a: { b: 1 }, c: 2 }
  expect(objectIncludes(condition)(input)).toBeTruthy()
})

test('with wrong input', () => {
  const condition = { a: { b: 1 } }
  expect(() => objectIncludes(condition)(null as any)).toThrow()
})

---------------

objOf


objOf<T, K extends PropertyKey>(key: K): (value: T) => { [P in K]: T }

It creates an object with a single key-value pair.

const result = R.objOf('foo')('bar')
// => {foo: 'bar'}

Try this R.objOf example in Rambda REPL

All TypeScript definitions
objOf<T, K extends PropertyKey>(key: K): (value: T) => { [P in K]: T };
R.objOf source
export function objOf(key) {
  return value => ({ [key]: value })
}
Tests
import { objOf } from './objOf'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(42, objOf('foo'))
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expect(result).toEqual({ foo: 42 })
})

---------------

omit


omit<
	S extends string,
	Keys extends PickStringToPickPath<S>,
>(propsToPick: S): <U extends Partial<Record<ElementOf<Keys>, any>>>(
	obj: ElementOf<Keys> extends keyof U ? U : never
) => ElementOf<Keys> extends keyof U ? MergeTypes<Omit<U, ElementOf<Keys>>> : never

It returns a partial copy of an obj without propsToOmit properties.

const obj = {a: 1, b: 2, c: 3}
const propsToOmit = 'a,c,d'
const propsToOmitList = ['a', 'c', 'd']

const result = [
  R.omit(propsToOmit)(obj),
  R.omit(propsToOmitList)(obj)
]
// => [{b: 2}, {b: 2}]

Try this R.omit example in Rambda REPL

All TypeScript definitions
omit<
	S extends string,
	Keys extends PickStringToPickPath<S>,
>(propsToPick: S): <U extends Partial<Record<ElementOf<Keys>, any>>>(
	obj: ElementOf<Keys> extends keyof U ? U : never
) => ElementOf<Keys> extends keyof U ? MergeTypes<Omit<U, ElementOf<Keys>>> : never;
omit<const Keys extends PropertyKey[]>(propsToPick: Keys): <
	U extends Partial<Record<ElementOf<Keys>, any>>
>(
	obj: ElementOf<Keys> extends keyof U ? U : never
) => ElementOf<Keys> extends keyof U ? MergeTypes<Omit<U, ElementOf<Keys>>> : never;
R.omit source
import { createPath } from './_internals/createPath.js'

export function _includes(x, list) {
  let index = -1
  const { length } = list

  while (++index < length) {
    if (String(list[index]) === String(x)) {
      return true
    }
  }

  return false
}

export function omit(propsToOmit) {
  return obj => {
    if (!obj) {
      return undefined
    }

    const propsToOmitValue = createPath(propsToOmit, ',')
    const willReturn = {}

    for (const key in obj) {
      if (!_includes(key, propsToOmitValue)) {
        willReturn[key] = obj[key]
      }
    }

    return willReturn
  }
}
Tests
import { omit } from './omit'
import { pipe } from './pipe'

test('happy', () => {
  const input = { a: 'foo', b: 2, c: 3 }
  const result = pipe(input, omit('a,b'))
  expectTypeOf(result).toEqualTypeOf<{ c: number }	>()
  expect(result).toEqual({ c: 3 })
})

test('with string as condition', () => {
  const obj = { a: 1, b: 2, c: 3 }
  expect(omit('a,c')(obj)).toEqual({ b: 2 })
})

test('with array as condition', () => {
  expect(omit(['a', 'c', 'd'])({ a: 'foo', b: 'bar', c: 'baz' } as any)).toEqual({ b: 'bar' })
})

---------------

partition


partition<T, S extends T>(
  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
): (data: ReadonlyArray<T>) => [Array<S>, Array<Exclude<T, S>>]

It will return array of two arrays according to predicate function. The first member holds all instances of input that pass the predicate function, while the second member - those who doesn't.

const list = [1, 2, 3]
const predicate = x => x > 2

const result = R.partition(predicate)(list)

const expected = [[3], [1, 2]]
// `result` is equal to `expected`

Try this R.partition example in Rambda REPL

All TypeScript definitions
partition<T, S extends T>(
  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
): (data: ReadonlyArray<T>) => [Array<S>, Array<Exclude<T, S>>];
partition<T>(
  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
): (data: ReadonlyArray<T>) => [Array<T>, Array<T>];
R.partition source
export function partition(predicate) {
  return list => {
		const yes = []
		const no = []
		let counter = -1
	
		while (counter++ < list.length - 1) {
			if (predicate(list[counter], counter)) {
				yes.push(list[counter])
			} else {
				no.push(list[counter])
			}
		}
	
		return [yes, no]
  }
}
Tests
import { partition } from './partition'
import { pipe } from './pipe'

test('happy', () => {
  const list = [1, 2, 3, 4]
  const result = pipe(list, partition((x: number) => x > 2))
  expectTypeOf(result).toEqualTypeOf<[number[], number[]]>()
  expect(result).toEqual([[3, 4], [1, 2]])
})

---------------

partitionObject


partitionObject<T extends unknown, S extends T>(
  predicate: (value: T, prop: string, obj: Record<string, T>) => value is S,
): (obj: Record<string, T>) => [Record<string, S>, Record<string, Exclude<T, S>>]

It returns an array containing two objects. The first object holds all properties of the input object for which the predicate returns true, while the second object holds those that do not.

const obj = {a: 1, b: 2, c: 3}
const predicate = x => x > 2

const result = R.partition(predicate)(obj)

const expected = [{c: 3},  {a: 1, b: 2}]
// `result` is equal to `expected`

Try this R.partitionObject example in Rambda REPL

All TypeScript definitions
partitionObject<T extends unknown, S extends T>(
  predicate: (value: T, prop: string, obj: Record<string, T>) => value is S,
): (obj: Record<string, T>) => [Record<string, S>, Record<string, Exclude<T, S>>];
partitionObject<T extends unknown>(
  predicate: (value: T, prop: string, obj: Record<string, T>) => boolean,
): (obj: Record<string, T>) => [Record<string, T>, Record<string, T>];
R.partitionObject source
export function partitionObject(predicate) {
	return obj => {
  const yes = {}
  const no = {}
  Object.entries(obj).forEach(([prop, value]) => {
    if (predicate(value, prop)) {
      yes[prop] = value
    } else {
      no[prop] = value
    }
  })

  return [yes, no]
}
}
Tests
import { partitionObject } from './partitionObject'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 2 }, partitionObject((x, prop) => x > 1 || prop === 'c'))
  expectTypeOf(result).toEqualTypeOf<[Record<string, number>, Record<string, number>]>()
  expect(result).toEqual([{ b: 2 }, { a: 1 }])
})

---------------

path


path<S, K0 extends string & keyof S>(path: `${K0}`): (obj: S) => S[K0]

If pathToSearch is 'a.b' then it will return 1 if obj is {a:{b:1}}.

It will return undefined, if such path is not found.

💥 String annotation of pathToSearch is one of the differences between Rambda and Ramda.

const obj = {a: {b: 1}}
const pathToSearch = 'a.b'
const pathToSearchList = ['a', 'b']

const result = [
  R.path(pathToSearch)(obj),
  R.path(pathToSearchList)(obj),
  R.path('a.b.c.d')(obj)
]
// => [1, 1, undefined]

Try this R.path example in Rambda REPL

All TypeScript definitions
path<S, K0 extends string & keyof S>(path: `${K0}`): (obj: S) => S[K0];
path<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(path: `${K0}.${K1}`): (obj: S) => S[K0][K1];
path<
  S,
  K0 extends keyof S,
  K1 extends keyof S[K0],
  K2 extends keyof S[K0][K1]
>(path: [K0, K1, K2]): (obj: S) => S[K0][K1][K2];
path<
  S,
  K0 extends string & keyof S,
  K1 extends string & keyof S[K0],
  K2 extends string & keyof S[K0][K1]
>(path: `${K0}.${K1}.${K2}`): (obj: S) => S[K0][K1][K2];
...
...
R.path source
import { createPath } from './_internals/createPath.js'

export function path(pathInput) {
	return (obj)  => {
		if (!obj) {
			return undefined
		}
		let willReturn = obj
		let counter = 0
	
		const pathArrValue = createPath(pathInput)
	
		while (counter < pathArrValue.length) {
			if (willReturn === null || willReturn === undefined) {
				return undefined
			}
			if (willReturn[pathArrValue[counter]] === null) {
				return undefined
			}
	
			willReturn = willReturn[pathArrValue[counter]]
			counter++
		}
	
		return willReturn
	}
}
Tests
import { path } from './path'
import { pipe } from './pipe'

test('happy', () => {
  const input = { a: { b: { c: true } } }
  const result = pipe(input, path(['a', 'b']))
	expectTypeOf(result).toEqualTypeOf<{ c: boolean }>()
  expect(result).toEqual({ c: true })
})

---------------

pathSatisfies


pathSatisfies<S, K0 extends string & keyof S>(
  predicate: (x: S[K0]) => boolean,
  path: [K0]
): (obj: S) => boolean
const result = R.pathSatisfies(
  x => x > 0,
  ['a', 'b', 'c'],
  {a: {b: {c: 1}}}
)
// => true

Try this R.pathSatisfies example in Rambda REPL

All TypeScript definitions
pathSatisfies<S, K0 extends string & keyof S>(
  predicate: (x: S[K0]) => boolean,
  path: [K0]
): (obj: S) => boolean;
pathSatisfies<S, K0 extends string & keyof S>(
  predicate: (x: S[K0]) => boolean,
  path: `${K0}`
): (obj: S) => boolean;
pathSatisfies<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  predicate: (x: S[K0][K1]) => boolean,
  path: [K0, K1]
): (obj: S) => boolean;
pathSatisfies<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  predicate: (x: S[K0][K1]) => boolean,
  path: `${K0}.${K1}`
): (obj: S) => boolean;
...
...
R.pathSatisfies source
import { path } from './path.js'

export function pathSatisfies(fn, pathInput) {
  return obj => Boolean(fn(path(pathInput)(obj)))
}
Tests
import { pathSatisfies } from './pathSatisfies'
import { pipe } from './pipe'

const isPositive = (n: number) => n > 0

test('returns true if the specified object path satisfies the given predicate', () => {
	const result = pipe({ x: { y: 1 } },pathSatisfies(isPositive, ['x', 'y']))
  expect(result).toBe(true)
	expectTypeOf(result).toEqualTypeOf<boolean>()
})

test('returns false if the specified path does not exist', () => {
	// @ts-expect-error
  expect(pipe({ x: { z: 42 } }, pathSatisfies(isPositive, ['x', 'y']))).toBe(false)
	// @ts-expect-error
  expect(pipe({ x: { z: 42 } }, pathSatisfies(isPositive, 'x.y'))).toBe(false)
})

test('returns false otherwise', () => {
  expect(pipe({ x: { y: 0 } }, pathSatisfies(isPositive, ['x', 'y']))).toBe(false)
})

---------------

permutations


permutations<T>(list: T[]): T[][]
const result = R.permutations(
	[1, 2]
)
// => [[1, 2], [2, 1]]

Try this R.permutations example in Rambda REPL

All TypeScript definitions
permutations<T>(list: T[]): T[][];
R.permutations source
import { cloneList } from './_internals/cloneList.js'

/**
 * Source:
 * https://github.com/denoland/std/blob/main/collections/permutations.ts
 */
export function permutations(inputArray) {
  const result = [];
  const array = cloneList(inputArray);
  const k = array.length;
  if (k === 0) {
    return result;
  }

  const c = new Array(k).fill(0);

  result.push([...array]);

  let i = 1;

  while (i < k) {
    if (c[i] < i) {
      if (i % 2 === 0) {
        [array[0], array[i]] = [array[i], array[0]]
      } else {
        [array[c[i]], array[i]] = [array[i], array[c[i]]]
      }

      result.push([...array]);

      c[i] += 1;
      i = 1;
    } else {
      c[i] = 0;
      i += 1;
    }
  }

  return result;
}

---------------

pick


pick<K extends PropertyKey>(propsToPick: K[]): <T extends Partial<Record<K, any>>>(input: K extends keyof T ? T : never) => MergeTypes<Pick<T, K>>

It returns a partial copy of an input containing only propsToPick properties.

input can be either an object or an array.

String annotation of propsToPick is one of the differences between Rambda and Ramda.

💥 Typescript Note: Pass explicit type annotation when used with R.pipe/R.compose for better type inference

const obj = {
  a : 1,
  b : false,
  foo: 'cherry'
}
const propsToPick = 'a,foo'
const propsToPickList = ['a', 'foo']

const result = [
  R.pick(propsToPick)(obj),
  R.pick(propsToPickList)(obj),
  R.pick('a,bar')(obj),
  R.pick('bar')(obj),
]

const expected = [
  {a:1, foo: 'cherry'},
  {a:1, foo: 'cherry'},
  {a:1},
  {},
]
// => `result` is equal to `expected`

Try this R.pick example in Rambda REPL

All TypeScript definitions
pick<K extends PropertyKey>(propsToPick: K[]): <T extends Partial<Record<K, any>>>(input: K extends keyof T ? T : never) => MergeTypes<Pick<T, K>>;
pick<S extends string, Keys extends PickStringToPickPath<S>>(propsToPick: S): <T extends Partial<Record<ElementOf<Keys>, any>>>(input: ElementOf<Keys> extends keyof T ? T : never) => ElementOf<Keys> extends keyof T ? MergeTypes<Pick<T, ElementOf<Keys>>> : never;
R.pick source
import { createPath } from './_internals/createPath.js'

export function pick(propsToPick) {
  return input => {
    if (!input === null) {
      return undefined
    }
    const keys = createPath(propsToPick, ',')
    const willReturn = {}
    let counter = 0

    while (counter < keys.length) {
      if (keys[counter] in input) {
        willReturn[keys[counter]] = input[keys[counter]]
      }
      counter++
    }

    return willReturn
  }
}
Tests
import { pick } from './pick'
import { pipe } from './pipe'

test('happy', () => {
  const input = { a: 'foo', c: 3 }
  const result = pipe(input, pick('a,c'))
  expectTypeOf(result.a).toEqualTypeOf<string>()
  expectTypeOf(result.c).toEqualTypeOf<number>()
  expect(result).toEqual({ a: 'foo', c: 3 })
})

test('props to pick is a string', () => {
  const obj = { a: 1, b: 2, c: 3 }
  expect(pick('a,c')(obj)).toEqual({ a: 1, c: 3 })
})

test('when prop is missing', () => {
  const obj = { a: 1, b: 2, c: 3 }
  expect(pick('a,d,f')(obj as any)).toEqual({ a: 1 })
})

test('props to pick is an array', () => {
  expect(pick(['a', 'c'])({ a: 'foo', b: 'bar' } as any)).toEqual({ a: 'foo' })
})

---------------

pipe


pipe<A, B>(value: A, op1: (input: A) => B): B

It performs left-to-right function composition, where first argument is the input for the chain of functions.

This is huge difference from Ramda.pipe where input is passed like R.pipe(...fns)(input). Here we have R.pipe(input, ...fns).

It has much better TypeScript support than Ramda.pipe and this is the reason why Rambda goes in this direction.

const result = R.pipe(
  [1, 2, 3],
  R.filter(x => x > 1),
  R.map(x => x*10),
)
// => [20, 30]

Try this R.pipe example in Rambda REPL

All TypeScript definitions
pipe<A, B>(value: A, op1: (input: A) => B): B;
pipe<A, B, C>(
  value: A,
  op1: (input: A) => B,
  op2: (input: B) => C,
): C;
pipe<A, B, C, D>(
  value: A,
  op1: (input: A) => B,
  op2: (input: B) => C,
  op3: (input: C) => D,
): D;
pipe<A, B, C, D, E>(
  value: A,
  op1: (input: A) => B,
  op2: (input: B) => C,
  op3: (input: C) => D,
  op4: (input: D) => E,
): E;
...
...
R.pipe source
import { reduce } from './reduce.js'

export function _arity(n, fn) {
  switch (n) {
    case 0:
      return function () {
        return fn.apply(this, arguments)
      }
    case 1:
      return function (a0) {
        return fn.apply(this, arguments)
      }
    case 2:
      return function (a0, a1) {
        return fn.apply(this, arguments)
      }
    case 3:
      return function (a0, a1, a2) {
        return fn.apply(this, arguments)
      }
    case 4:
      return function (a0, a1, a2, a3) {
        return fn.apply(this, arguments)
      }
    case 5:
      return function (a0, a1, a2, a3, a4) {
        return fn.apply(this, arguments)
      }
    case 6:
      return function (a0, a1, a2, a3, a4, a5) {
        return fn.apply(this, arguments)
      }
    case 7:
      return function (a0, a1, a2, a3, a4, a5, a6) {
        return fn.apply(this, arguments)
      }
    case 8:
      return function (a0, a1, a2, a3, a4, a5, a6, a7) {
        return fn.apply(this, arguments)
      }
    case 9:
      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) {
        return fn.apply(this, arguments)
      }
    case 10:
      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {
        return fn.apply(this, arguments)
      }
    default:
      throw new Error(
        'First argument to _arity must be a non-negative integer no greater than ten',
      )
  }
}

function _pipe(f, g) {
  return function () {
    return g.call(this, f.apply(this, arguments))
  }
}

function pipeFn() {
  if (arguments.length === 0) {
    throw new Error('pipe requires at least one argument')
  }

  return _arity(
    arguments[0].length,
    reduce(
      _pipe,
      arguments[0],
    )(Array.prototype.slice.call(arguments, 1, Number.POSITIVE_INFINITY)),
  )
}

export function pipe(...inputs) {
  const [input, ...fnList] = inputs

  return pipeFn(...fnList)(input)
}
Tests
import { filter } from './filter'
import { map } from './map'
import { pipe } from './pipe'
import { split } from './split'

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    filter(x => x > 1),
    map(x => x * 10),
    map(x => x + 1),
  )
  expect(result).toEqual([21, 31])
})

test('split test', () => {
  const tableData = `id,title,year
1,The First,2001
2,The Second,2020
3,The Third,2018`

  const result = pipe(tableData, split('\n'), map(split(',')))
  expectTypeOf(result).toEqualTypeOf<string[][]>()
  expect(result).toEqual([
    ['id', 'title', 'year'],
    ['1', 'The First', '2001'],
    ['2', 'The Second', '2020'],
    ['3', 'The Third', '2018'],
  ])
})

test('R.pipe type test', () => {
  const obj = { a: 'foo', b: 'bar' }
  const result = pipe(
    obj,
    x => ({ a: x.a.length + x.b.length }),
    x => ({ ...x, b: x.a + 'foo' }),
    x => ({ ...x, c: x.b + 'bar' }),
  )
  expectTypeOf(result.a).toEqualTypeOf<number>()
  expectTypeOf(result.b).toEqualTypeOf<string>()
  expectTypeOf(result.c).toEqualTypeOf<string>()
  expect(result.a > 0).toBeTruthy()
})

---------------

pipeAsync


pipeAsync<A, B>(input: A, fn0: (x: Awaited<A>) => B) : B

It accepts input as first argument and series of functions as next arguments. It is same as R.pipe but with support for asynchronous functions.

const result = await R.pipeAsync(
  100,
  async x => {
    await R.delay(100)
    return x + 2
  },
  x => x +2,
  async x => {
    const delayed = await R.delay(100)
    return delayed + x
  }
)
// `result` resolves to `RAMBDAX_DELAY104`

Try this R.pipeAsync example in Rambda REPL

All TypeScript definitions
pipeAsync<A, B>(input: A, fn0: (x: Awaited<A>) => B) : B;
pipeAsync<A, B, C>(input: A, fn0: (x: Awaited<A>) => B, fn1: (x: Awaited<B>) => C) : C;
pipeAsync<A, B, C, D>(input: A, fn0: (x: Awaited<A>) => B, fn1: (x: Awaited<B>) => C, fn2: (x: Awaited<C>) => D) : D;
pipeAsync<A, B, C, D, E>(input: A, fn0: (x: Awaited<A>) => B, fn1: (x: Awaited<B>) => C, fn2: (x: Awaited<C>) => D, fn3: (x: Awaited<D>) => E) : E;
...
...
R.pipeAsync source
import { type } from './type.js'

export async function pipeAsync(input, ...fnList) {
  let willReturn = input
  for (const fn of fnList) {
    const initialResult = fn(willReturn)
    willReturn =
      type(initialResult) === 'Promise' ? await initialResult : initialResult
  }
  return willReturn
}
Tests
import { delay } from './delay'
import { pipeAsync } from './pipeAsync'

test('happy', async () => {
  const result = await pipeAsync(
    4,
    async x => {
      await delay(100)
      return x + 1
    },
    x => Promise.resolve([x]),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([5])
})

---------------

pluck


pluck<T, K extends keyof T>(property: K): (list: T[]) => T[K][]

It returns list of the values of property taken from the all objects inside list. Basically, this is R.map(R.prop(property)).

💥 Typescript Note: Pass explicit type annotation when used with R.pipe/R.compose for better type inference

const list = [{a: 1}, {a: 2}, {b: 3}]
const property = 'a'

const result = R.pluck(property)(list)
// => [1, 2]

Try this R.pluck example in Rambda REPL

All TypeScript definitions
pluck<T, K extends keyof T>(property: K): (list: T[]) => T[K][];
pluck<K extends PropertyKey>(prop: K): {
  <U extends O[keyof O], UK extends keyof U, O extends Record<string, any>>(obj: K extends UK ? O : never): { [OK in keyof O]: O[OK][K] };
  <U extends readonly unknown[] | Record<K, any>>(list: readonly U[]): U extends readonly (infer T)[] ? T[] : U extends Record<K, infer T> ? T[] : never;
};
...
...
R.pluck source
export function pluck(property) {
  return list => {
    const willReturn = []

    list.forEach(x => {
      if (x[property] !== undefined) {
        willReturn.push(x[property])
      }
    })

    return willReturn
  }
}
Tests
import { pluck } from './pluck'
import { pipe } from './pipe'

test('happy', () => {
  const input = [{ a: 1, b: 'foo' }, { a: 2, b: 'bar' }]
  const result = pipe(input, pluck('b'))
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['foo', 'bar'])
})

test('with undefined', () => {
  expect(pluck(undefined as any)([{ a: 1 }, { a: 2 }, { b: 1 }])).toEqual([])
})

---------------

prepend


prepend<T>(xToPrepend: T): (iterable: T[]) => T[]

It adds element x at the beginning of list.

const result = R.prepend('foo')(['bar', 'baz'])
// => ['foo', 'bar', 'baz']

Try this R.prepend example in Rambda REPL

All TypeScript definitions
prepend<T>(xToPrepend: T): (iterable: T[]) => T[];
R.prepend source
export function prepend(x) {
  return list => [x].concat(list)
}
Tests
import { prepend } from './prepend'

test('happy', () => {
  const result = prepend('yes')(['foo', 'bar', 'baz'])
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['yes', 'foo', 'bar', 'baz'])
})

test('with empty list', () => {
  expect(prepend('foo')([])).toEqual(['foo'])
})

[![---------------](https://raw.githubusercontent.com/selfref

Truncated — view the full README on GitHub.

fp
functional
functional-programming
functions
lodash
ramda
typescript
utility
utils

selfrefactor/rambda

Typescript focused FP library similar to Remeda and Ramda

TypeScript

1,758

2,279 commits

updated Sep 22, 2026

See the code

README

Rambda

Rambda is TypeScript-focused utility library similar to Remeda, Ramda and Radashi. - Documentation site

Commit activity Library size install size PR's Welcome GitHub contributors

❯ Example use

import { pipe, filter, map } from 'rambda'

const result = pipe(
  [1, 2, 3, 4],
  filter(x => x > 2),
  map(x => x * 2),
)
//=> [6, 8]

You can test this example in Rambda's REPL

---------------

❯ Rambda's features

❯ Goals

Typescript focus

Mixing Functional Programming and TypeScript is not easy.

One way to solve this is to focus what can be actually achieved and refrain from what is not possible.

R.pipe as the main way to use Rambda

  • All methods are meant to be used as part of R.pipe chain

  • This is the main purpose of functional programming, i.e. to pass data through a chain of functions.

  • Having R.pipe(input, ...fns) helps TypeScript to infer the types of the input and the output.

Here is one example why R.pipe is better than Ramda.pipe:

const list = [1, 2, 3];

it('within pipe', () => {
	const result = pipe(
		list,
		filter((x) => {
			x; // $ExpectType number
			return x > 1;
		}),
	);
	result; // $ExpectType number[]
});
it('within Ramda.pipe requires explicit types', () => {
	Ramda.pipe(
		(x) => x,
		filter<number>((x) => {
			x; // $ExpectType number
			return x > 1;
		}),
		filter((x: number) => {
			x; // $ExpectType number
			return x > 1;
		}),
	)(list);
});

❗ IMPORTANT - all methods are tested to deliver correct types when they are part of R.pipe/R.pipeAsync chains.

In other words:

R.filter(x => x > 1)([1,2,3])

might trigger TS error as it not the same as


R.pipe([1,2,3], R.filter(x => x > 1)

❗ All methods are curried

There is one way to use Rambda methods and it is with currying, i.e. using R.filter(fn, list) will not work as it is inteded to be R.filter(fn)(list).

The reason is that all methods are supposed to be used inside R.pipe. After all, building chains is the very base of functional programming.

Of course, there is value in supporting the case where you can pass all inputs at once, but I find that the price in terms of maintainability is not worth it.

Keep only the most useful methods

The idea is to give TypeScript users only the most useful methods and let them implement the rest. No magic logic methods that are hard to remember. You shouldn't need to read the documentation to understand what a method does. Its name and signature should be enough.

  • Methods that are simply to remember only by its name. Complex logic shouldn't be part of utility library, but part of your codebase.

  • Keep only methods which are both useful and which behaviour is obvious from its name. For example, R.innerJoin is kept, but R.identical, R.move is removed. Methods such as R.toLower, R.length provide little value. Such method are omitted from Rambda on purpose.

  • Some generic methods such as curry and assoc is not easy to be expressed in TypeScript. For this reason Rambda omits such methods.

  • No R.cond or R.ifElse as they make the chain less readable.

  • No R.length as it adds very little value.

  • No R.difference as user must remember the order of the inputs, i.e. which is compared to and which is compared against.

One way to use each method

Because of the focus on R.pipe, there is only one way to use each method. This helps with testing and also with TypeScript definitions.

  • All methods that 2 inputs, will have to be called with R.methodName(input1)(input2)
  • All methods that 3 inputs, will have to be called with R.methodName(input1, input2)(input3)

Deno support

import * as R from "https://deno.land/x/rambda/mod.ts";

R.filter(x => x > 1)([1, 2, 3])

Dot notation for R.path

Standard usage of R.path is R.path(['a', 'b'])({a: {b: 1} }).

In Rambda you have the choice to use dot notation(which is arguably more readable):

R.path('a.b')({a: {b: 1} })

Please note that since path input is turned into array, i.e. if you want R.path(['a','1', 'b'])({a: {'1': {b: 2}}}) to return 2, you will have to pass array path, not string path. If you pass a.1.b, it will turn path input to ['a', 1, 'b'].

Comma notation for R.pick and R.omit

Similar to dot notation, but the separator is comma(,) instead of dot(.).

R.pick('a,b', {a: 1 , b: 2, c: 3} })
// No space allowed between properties

Differences between Rambda and Ramda

Up until version 9.4.2, the aim of Rambda was to match as much as possible the Ramda API.

You can find documentation site of Rambda version 9.4.2 is here.

From version 10.0.0 onwards, Rambda is no longer aiming to be drop-in replacement for Ramda.

---------------

API

addProp


addProp<T extends object, P extends PropertyKey, V extends unknown>(
	prop: P,
	value: V
): (obj: T) => MergeTypes<T & Record<P, V>>

It adds new key-value pair to the object.

const result = R.pipe(
	{ a: 1, b: 'foo' },
	R.addProp('c', 3)
)
// => { a: 1, b: 'foo', c: 3 }

Try this R.addProp example in Rambda REPL

All TypeScript definitions
addProp<T extends object, P extends PropertyKey, V extends unknown>(
	prop: P,
	value: V
): (obj: T) => MergeTypes<T & Record<P, V>>;
R.addProp source
export function addProp(key, value) {
  return obj => ({ ...obj, [key]: value })
}
Tests
import { addProp } from './addProp'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 'foo' }, addProp('c', 3))
  expectTypeOf(result.a).toEqualTypeOf<number>()
  expectTypeOf(result.b).toEqualTypeOf<string>()
  expectTypeOf(result.c).toEqualTypeOf<number>()
  expect(result).toEqual({ a: 1, b: 'foo', c: 3 })
})

---------------

addPropToObjects


addPropToObjects<
  T extends object,
  K extends string,
  R
>(
	property: K,
  fn: (input: T) => R
): (list: T[]) => MergeTypes<T & { [P in K]: R }>[]

It receives list of objects and add new property to each item.

The value is based on result of fn function, which receives the current object as argument.

const result = R.pipe(
	[
		{a: 1, b: 2},
		{a: 3, b: 4},
	],
	R.addPropToObjects(
		'c',
		(x) => String(x.a + x.b),
	)
)
// => [{a: 1, b: 2, c: '3'}, {a: 3, b: 4, c: '7'}]

Try this R.addPropToObjects example in Rambda REPL

All TypeScript definitions
addPropToObjects<
  T extends object,
  K extends string,
  R
>(
	property: K,
  fn: (input: T) => R
): (list: T[]) => MergeTypes<T & { [P in K]: R }>[];
R.addPropToObjects source
import { mapFn } from './map.js'

export function addPropToObjects (
	property, 
	fn
){
	return listOfObjects => mapFn(
		(obj) => ({
			...(obj),
			[property]: fn(obj)
		}), 
		listOfObjects
	)
}
Tests
import { pipe } from './pipe'
import { addPropToObjects } from './addPropToObjects'

test('R.addPropToObjects', () => {
  let result = pipe(
    [
      {a: 1, b: 2},
      {a: 3, b: 4},
    ],
    addPropToObjects(
      'c',
      (x: { a: number; b: number }) => String(x.a + x.b),
    )
  )
  expectTypeOf(result).toEqualTypeOf<{ a: number; b: number; c: string }[]>()
  expect(result).toEqual([
    { a: 1, b: 2, c: '3' },
    { a: 3, b: 4, c: '7' },
  ])
})

---------------

all


all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true, if all members of array list returns true, when applied as argument to predicate function.

const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > -1

const result = R.pipe(
	list,
	R.all(predicate)
) // => true

Try this R.all example in Rambda REPL

All TypeScript definitions
all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.all source
export function all(predicate) {
  return list => {
    for (let i = 0; i < list.length; i++) {
      if (!predicate(list[i])) {
        return false
      }
    }

    return true
  }
}
Tests
import { all } from './all'
import { pipe } from './pipe'

const list = [0, 1, 2, 3, 4]

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    all((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 0
    }),
  )
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('when true', () => {
  const fn = (x: number) => x > -1

  expect(all(fn)(list)).toBeTruthy()
})

test('when false', () => {
  const fn = (x: number) => x > 2

  expect(all(fn)(list)).toBeFalsy()
})

---------------

allPass


allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F

It returns true, if all functions of predicates return true, when input is their argument.

const list = [[1, 2, 3, 4], [3, 4, 5]]
const result = R.pipe(
	list,
	R.filter(R.allPass([R.includes(2), R.includes(3)]))
) // => [[1, 2, 3, 4]]

Try this R.allPass example in Rambda REPL

All TypeScript definitions
allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F;
R.allPass source
export function allPass(predicates) {
  return input => {
    let counter = 0
    while (counter < predicates.length) {
      if (!predicates[counter](input)) {
        return false
      }
      counter++
    }

    return true
  }
}
Tests
import { allPass } from './allPass'
import { filter } from './filter'
import { pipe } from './pipe'

const list = [
  [1, 2, 3, 4],
  [3, 4, 5],
]

test('happy', () => {
  const result = pipe(list, filter(allPass([(x: number[]) => x.includes(2), (x: number[]) => x.includes(3)])))
  expectTypeOf(result).toEqualTypeOf<number[][]>()
  expect(result).toEqual([[1, 2, 3, 4]])
})

test('when returns false', () => {
  const result = pipe(list, filter(allPass([(x: number[]) => x.includes(12), (x: number[]) => x.includes(31)])))
  expect(result).toEqual([])
})

---------------

any


any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true, if at least one member of list returns true, when passed to a predicate function.

const list = [1, 2, 3]
const predicate = x => x * x > 8
R.any(predicate)(list)
// => true

Try this R.any example in Rambda REPL

All TypeScript definitions
any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.any source
export function any(predicate) {
  return list => {
    let counter = 0
    while (counter < list.length) {
      if (predicate(list[counter], counter)) {
        return true
      }
      counter++
    }

    return false
  }
}
Tests
import { any } from './any'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    any((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 2
    }),
  )
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

---------------

anyPass


anyPass<T, TF1 extends T, TF2 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2],
): (a: T) => a is TF1 | TF2

It accepts list of predicates and returns a function. This function with its input will return true, if any of predicates returns true for this input.

const isBig = x => x > 20
const isOdd = x => x % 2 === 1
const input = 11

const fn = R.anyPass(
  [isBig, isOdd]
)

const result = fn(input)
// => true

Try this R.anyPass example in Rambda REPL

All TypeScript definitions
anyPass<T, TF1 extends T, TF2 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2],
): (a: T) => a is TF1 | TF2;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3],
): (a: T) => a is TF1 | TF2 | TF3;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3],
): (a: T) => a is TF1 | TF2 | TF3;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 extends T>(
  predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3, (a: T) => a is TF4],
): (a: T) => a is TF1 | TF2 | TF3 | TF4;
...
...
R.anyPass source
export function anyPass(predicates) {
  return input => {
    let counter = 0
    while (counter < predicates.length) {
      if (predicates[counter](input)) {
        return true
      }
      counter++
    }
    return false
  }
}
Tests
import { anyPass } from "./anyPass"
import { filter } from "./filter"
import { pipe } from "./pipe"

test("happy", () => {
  const rules = [(x: any) => typeof x === "string", (x: any) => x > 10]
  const predicate = anyPass(rules)
  expect(predicate("foo")).toBeTruthy()
  expect(predicate(6)).toBeFalsy()
})

test("happy 2", () => {
  const rules = [(x: any) => typeof x === "string", (x: any) => x > 10]
  expect(anyPass(rules)(11)).toBeTruthy()
  expect(anyPass(rules)(undefined)).toBeFalsy()
})

const obj = {
  a: 1,
  b: 2,
}

test("when returns true", () => {
  const conditionArr = [(val: any) => val.a === 1, (val: any) => val.a === 2]
  expect(anyPass(conditionArr)(obj)).toBeTruthy()
})

test("when returns false", () => {
  const conditionArr = [(val: any) => val.a === 2, (val: any) => val.b === 3]
  expect(anyPass(conditionArr)(obj)).toBeFalsy()
})

test("with empty predicates list", () => {
	const result = pipe(3, anyPass([]))
	expectTypeOf(result).toEqualTypeOf<unknown>()
  expect(result).toBeFalsy()
})

test("issue #604", () => {
  const plusEq = (w: number, x: number, y: number, z: number) => w + x === y + z
  const result = anyPass([plusEq])(3, 3, 3, 3)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(false)
})

test("issue #642", () => {
  const isGreater = (num: number) => num > 5
  const pred = anyPass([isGreater])
  const xs = [0, 1, 2, 3]
  const filtered1 = filter(pred)(xs)
  expectTypeOf(filtered1).toEqualTypeOf<number[]>()
  const filtered2 = xs.filter(pred)
  expectTypeOf(filtered2).toEqualTypeOf<number[]>()
})

test("functions as a type guard", () => {
  const isString = (x: unknown): x is string => typeof x === "string"
  const isNumber = (x: unknown): x is number => typeof x === "number"
  const isBoolean = (x: unknown): x is boolean => typeof x === "boolean"
  const isStringNumberOrBoolean = anyPass([isString, isNumber, isBoolean])
  const aValue: unknown = 1
  if (isStringNumberOrBoolean(aValue)) {
    expectTypeOf(aValue).toEqualTypeOf<string | number | boolean>()
  }
})

---------------

append


append<T>(el: T): (list: readonly T[]) => T[]

It adds element x at the end of iterable.

const result = R.append('foo')(['bar', 'baz'])
// => ['bar', 'baz', 'foo']

Try this R.append example in Rambda REPL

All TypeScript definitions
append<T>(el: T): (list: readonly T[]) => T[];
append<T>(el: T): (list: T[]) => T[];
R.append source
import { cloneList } from './_internals/cloneList.js'

export function append(x) {
  return list => {
    const clone = cloneList(list)
    clone.push(x)

    return clone
  }
}
Tests
import { append } from './append'
import { prepend } from './prepend'
import { pipe } from './pipe'

const listOfNumbers = [1, 2, 3]

test('happy', () => {
  const result = pipe(listOfNumbers, append(4), prepend(0))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([0, 1, 2, 3, 4])
})

test('append to empty array', () => {
  expect(append('tests')([])).toEqual(['tests'])
})

test('with object', () => {
  const result = pipe([{ a: 1 }], append({ a: 10 }), prepend({ a: 20 }))
  expectTypeOf(result).toEqualTypeOf<{ a: number }[]>()
  expect(result).toEqual([{ a: 20 }, { a: 1 }, { a: 10 }])
})

---------------

ascend


ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering

Helper function to be used with R.sort to sort list in ascending order.

const result = R.pipe(
	[{a: 1}, {a: 2}, {a: 0}],
	R.sort(R.ascend(R.prop('a')))
)
// => [{a: 0}, {a: 1}, {a: 2}]

Try this R.ascend example in Rambda REPL

All TypeScript definitions
ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
R.ascend source
export function createCompareFunction(a, b, winner, loser) {
  if (a === b) {
    return 0
  }

  return a < b ? winner : loser
}

export function ascend(getFunction) {
	return (a, b) => {
  const aValue = getFunction(a)
  const bValue = getFunction(b)

  return createCompareFunction(aValue, bValue, -1, 1)
}
}
Tests
import { ascend } from './ascend'
import { descend } from './descend'
import { sort } from './sort'

test('ascend', () => {
  const result = sort(
    ascend((x: { a: number }) => x.a))(
    [{a:1}, {a:3}, {a:2}],
  )
  expectTypeOf(result).toEqualTypeOf<{ a: number }[]>()
  expect(result).toEqual([{a:1}, {a:2}, {a:3}])
})

test('descend', () => {
  const result = sort(
    descend((x: { a: number }) => x.a))(
    [{a:1}, {a:3}, {a:2}],
  )
  expect(result).toEqual([{a:3}, {a:2}, {a:1}])
})

---------------

assertType


assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U

It helps to make sure that input is from specific type. Similar to R.convertToType, but it actually checks the type of the input value. If fn input returns falsy value, then the function will throw an error.

All TypeScript definitions
assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U;
assertType<T>(fn: (x: T) => boolean): (x: T) => T;
R.assertType source
export function assertType(fn) {
  return (x) => {
    if (fn(x)) {
      return x
    }
    throw new Error('type assertion failed in R.assertType')
  }
}
Tests
import { assertType } from './assertType'
import { pipe } from './pipe'

type Book = {
  title: string
  year: number
}

type BookToRead = Book & {
  bookmarkFlag: boolean
}

function isBookToRead(book: Book): book is BookToRead {
  return (book as BookToRead).bookmarkFlag !== undefined
}

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    assertType((x: number[]) => x.length === 3),
  )
  expect(result).toEqual([1, 2, 3])
})

test('throw', () => {
  expect(() => {
    pipe(
      [1, 2, 3],
      assertType((x: number[]) => x.length === 4),
    )
  }).toThrow('type assertion failed in R.assertType')
})

test('R.assertType', () => {
  const result = pipe(
    { title: 'Book1', year: 2020, bookmarkFlag: true },
    assertType(isBookToRead),
  )
  expectTypeOf(result).toEqualTypeOf<BookToRead>()
  expect(result).toEqual({ title: 'Book1', year: 2020, bookmarkFlag: true })
})

---------------

checkObjectWithSpec


checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean

It returns true if all each property in conditions returns true when applied to corresponding property in input object.

const condition = R.checkObjectWithSpec({
  a : x => typeof x === "string",
  b : x => x === 4
})
const input = {
  a : "foo",
  b : 4,
  c : 11,
}

const result = condition(input)
// => true

Try this R.checkObjectWithSpec example in Rambda REPL

All TypeScript definitions
checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean;
R.checkObjectWithSpec source
export function checkObjectWithSpec(conditions) {
  return input => {
    let shouldProceed = true
    for (const prop in conditions) {
      if (!shouldProceed) {
        continue
      }
      const result = conditions[prop](input[prop])
      if (shouldProceed && result === false) {
        shouldProceed = false
      }
    }

    return shouldProceed
  }
}
Tests
import { checkObjectWithSpec } from './checkObjectWithSpec'
import { equals } from './equals'

test('happy', () => {
  const input = {
    a: 'foo',
    b: 'bar',
    x: 11,
    y: 19,
  }
  const conditions = {
    a: equals('foo'),
    b: equals('bar'),
  }
  const result = checkObjectWithSpec(conditions)(input)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('when true', () => {
  const result = checkObjectWithSpec({
    a: equals('foo'),
    b: equals('bar'),
  })({
    a: 'foo',
    b: 'bar',
    x: 11,
    y: 19,
  })
  expect(result).toBeTruthy()
})

test('when false | early exit', () => {
  let counter = 0
  const equalsFn = (expected: string) => (input: string) => {
    counter++
    return input === expected
  }
  const predicate = checkObjectWithSpec({
    a: equalsFn('foo'),
    b: equalsFn('baz'),
  })
  expect(
    predicate({
      a: 'notfoo',
      b: 'notbar',
    }),
  ).toBeFalsy()
  expect(counter).toBe(1)
})

---------------

compact


compact<T>(list: T[]): Array<StrictNonNullable<T>>

It removes null and undefined members from list or object input.

const result = R.pipe(
	{
		a: [ undefined, '', 'a', 'b', 'c'],
		b: [1,2, null, 0, undefined, 3],
		c: { a: 1, b: 2, c: 0, d: undefined, e: null, f: false },
	},
	x => ({
		a: R.compact(x.a),
		b: R.compact(x.b),
		c: R.compact(x.c)
	})
)
// => { a: ['a', 'b', 'c'], b: [1, 2, 3], c: { a: 1, b: 2, c: 0, f: false } }

Try this R.compact example in Rambda REPL

All TypeScript definitions
compact<T>(list: T[]): Array<StrictNonNullable<T>>;
compact<T extends object>(record: T): {
  [K in keyof T as Exclude<T[K], null | undefined> extends never
    ? never
    : K
  ]: Exclude<T[K], null | undefined>
};
R.compact source
import { isArray } from './_internals/isArray.js'
import { reject } from './reject.js'
import { rejectObject } from './rejectObject.js'

const isNullOrUndefined = x => x === null || x === undefined

export function compact(input){
	if(isArray(input)){
		return reject(isNullOrUndefined)(input)
	}
	return rejectObject(isNullOrUndefined)(input)
}
Tests
import { compact } from './compact'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(
    {
      a: [ undefined, '', 'a', 'b', 'c', null ],
      b: [1,2, null, 0, undefined, 3],
      c: { a: 1, b: 2, c: 0, d: undefined, e: null, f: false },
    },
    (x: { a: (string | undefined | null)[]; b: (number | null | undefined)[]; c: { a: number; b: number; c: number; d: undefined; e: null; f: boolean } }) => ({
      a: compact(x.a),
      b: compact(x.b),
      c: compact(x.c),
    })
  )
  expectTypeOf(result.a).toEqualTypeOf<string[]>()
  expectTypeOf(result.b).toEqualTypeOf<number[]>()
  expectTypeOf(result.c).toEqualTypeOf<{ a: number; b: number; c: number; f: boolean }>()
  expect(result.a).toEqual(['', 'a', 'b', 'c'])
  expect(result.b).toEqual([1,2,0,3])
  expect(result.c).toEqual({ a: 1, b: 2, c: 0, f: false })
})

---------------

complement


complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean

It returns inverted version of origin function that accept input as argument.

The return value of inverted is the negative boolean value of origin(input).

const fn = x => x > 5
const inverted = complement(fn)

const result = [
  fn(7),
  inverted(7)
] => [ true, false ]

Try this R.complement example in Rambda REPL

All TypeScript definitions
complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean;
R.complement source
export function complement(fn) {
  return (...input) => !fn(...input)
}
Tests
import { complement } from './complement'

test('happy', () => {
  const fn = complement((x: number) => x > 10)
  const result = fn(1)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('with multiple parameters', () => {
  const between = (a: number, b: number, c: number) => a < b && b < c
  const f = complement(between)
  expect(f(4, 5, 11)).toBeFalsy()
  expect(f(12, 2, 6)).toBeTruthy()
})

---------------

concat


concat<T>(x: T[]): (y: T[]) => T[]

It returns a new string or array, which is the result of merging x and y.

R.concat([1, 2])([3, 4]) // => [1, 2, 3, 4]
R.concat('foo')('bar') // => 'foobar'

Try this R.concat example in Rambda REPL

All TypeScript definitions
concat<T>(x: T[]): (y: T[]) => T[];
concat(x: string): (y: string) => string;
R.concat source
export function concat(x) {
  return y => (typeof x === 'string' ? `${x}${y}` : [...x, ...y])
}
Tests
import { concat } from './concat'
import { pipe } from './pipe'

const list1 = [1, 2, 3]
const list2 = [4, 5, 6]

test('R.concat', () => {
  const result = pipe(list1, concat(list2))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([4, 5, 6, 1, 2, 3])
  const resultString = pipe('foo', concat('list2'))
  expectTypeOf(resultString).toEqualTypeOf<string>()
  expect(resultString).toBe('list2foo')
})

---------------

convertToType


convertToType<T>(x: unknown) : T

It helps to convert a value to a specific type. It is useful when you have to overcome TypeScript's type inference.

All TypeScript definitions
convertToType<T>(x: unknown) : T;
R.convertToType source
export function convertToType(x) {
  return x
}

---------------

count


count<T>(predicate: (x: T) => boolean): (list: T[]) => number

It counts how many times predicate function returns true, when supplied with iteration of list.

const list = [{a: 1}, 1, {a:2}]
const result = R.count(x => x.a !== undefined)(list)
// => 2

Try this R.count example in Rambda REPL

All TypeScript definitions
count<T>(predicate: (x: T) => boolean): (list: T[]) => number;
R.count source
import { isArray } from './_internals/isArray.js'

export function count(predicate) {
  return list => {
    if (!isArray(list)) {
      return 0
    }

    return list.filter(x => predicate(x)).length
  }
}
Tests
import { count } from './count'
import { pipe } from './pipe'

const predicate = (x: any) => x.a !== undefined

test('happy', () => {
  const list = [1, 2, 3]
  const result = pipe(list, count((x: number) => x > 1))
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

test('with empty list', () => {
  expect(count(predicate)([])).toBe(0)
})

---------------

countBy


countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number }

It counts elements in a list after each instance of the input list is passed through transformFn function.

const list = [ 'a', 'A', 'b', 'B', 'c', 'C' ]

const result = countBy(x => x.toLowerCase())( list)
const expected = { a: 2, b: 2, c: 2 }
// => `result` is equal to `expected`

Try this R.countBy example in Rambda REPL

All TypeScript definitions
countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number };
R.countBy source
export function countBy(fn) {
  return list => {
    const willReturn = {}

    list.forEach(item => {
      const key = fn(item)
      if (!willReturn[key]) {
        willReturn[key] = 1
      } else {
        willReturn[key]++
      }
    })

    return willReturn
  }
}
Tests
import { countBy } from './countBy'
import { pipe } from './pipe'

const list = ['a', 'A', 'b', 'B', 'c', 'C']

test('happy', () => {
  const result = pipe(
    list,
    countBy((x: string) => x.toLowerCase()),
  )
  expectTypeOf(result.a).toEqualTypeOf<number>()
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expectTypeOf(result).toEqualTypeOf<{ [index: string]: number }>()
  expect(result).toEqual({ a: 2, b: 2, c: 2 })
})

---------------

createObjectFromKeys


createObjectFromKeys<const K extends readonly PropertyKey[], V>(
	fn: (key: K[number]) => V
): (keys: K) => { [P in K[number]]: V }
const result = R.createObjectFromKeys(
	(x, index) => `${x}-${index}`
)(['a', 'b', 'c'])
// => {a: 'a-0', b: 'b-1', c: 'c-2'}

Try this R.createObjectFromKeys example in Rambda REPL

All TypeScript definitions
createObjectFromKeys<const K extends readonly PropertyKey[], V>(
	fn: (key: K[number]) => V
): (keys: K) => { [P in K[number]]: V };
createObjectFromKeys<const K extends readonly PropertyKey[], V>(
	fn: (key: K[number], index: number) => V
): (keys: K) => { [P in K[number]]: V };
R.createObjectFromKeys source
export function createObjectFromKeys(fn) {
	return keys => {
		const result = {}
		keys.forEach((key, index) => {
			result[key] = fn(key, index)
		})

		return result
	}
}
Tests
import { createObjectFromKeys } from './createObjectFromKeys'

test('happy', () => {
  const result = createObjectFromKeys<['a', 'b'], string>((key: string, index: number) => key.toUpperCase() + index)(['a', 'b'])
  const expected = { a: 'A0', b: 'B1' }
  expect(result).toEqual(expected)
})

---------------

defaultTo


defaultTo<T>(defaultValue: T): (input: unknown) => T

It returns defaultValue, if all of inputArguments are undefined, null or NaN.

Else, it returns the first truthy inputArguments instance(from left to right).

💥 Typescript Note: Pass explicit type annotation when used with R.pipe/R.compose for better type inference

R.defaultTo('foo')('bar') // => 'bar'
R.defaultTo('foo'))(undefined) // => 'foo'

// Important - emtpy string is not falsy value
R.defaultTo('foo')('') // => 'foo'

Try this R.defaultTo example in Rambda REPL

All TypeScript definitions
defaultTo<T>(defaultValue: T): (input: unknown) => T;
R.defaultTo source
function isFalsy(input) {
  return input === undefined || input === null || Number.isNaN(input) === true
}

export function defaultTo(defaultArgument) {
  return input => isFalsy(input) ? defaultArgument : input
}
Tests
import { defaultTo } from './defaultTo'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe('bar' as unknown, defaultTo('foo'))
  expectTypeOf(result).toEqualTypeOf<string>()
  expect(result).toBe('bar')
})

test('with undefined', () => {
  expect(defaultTo('foo')(undefined)).toBe('foo')
})

test('with null', () => {
  expect(defaultTo('foo')(null)).toBe('foo')
})

test('with NaN', () => {
  expect(defaultTo('foo')(Number.NaN)).toBe('foo')
})

test('with empty string', () => {
  expect(defaultTo('foo')('')).toBe('')
})

test('with false', () => {
  expect(defaultTo('foo')(false)).toBeFalsy()
})

test('when inputArgument passes initial check', () => {
  expect(defaultTo('foo')('bar')).toBe('bar')
})

---------------

delay


delay(ms: number): Promise<'RAMBDA_DELAY'>

setTimeout as a promise that resolves to RAMBDA_DELAY string after ms milliseconds.

All TypeScript definitions
delay(ms: number): Promise<'RAMBDA_DELAY'>;
R.delay source
export const RAMBDA_DELAY = 'RAMBDA_DELAY'

export function delay(ms) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(RAMBDA_DELAY)
    }, ms)
  })
}

---------------

descend


descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering

Helper function to be used with R.sort to sort list in descending order.

const result = R.pipe(
	[{a: 1}, {a: 2}, {a: 0}],
	R.sort(R.descend(R.prop('a')))
)
// => [{a: 2}, {a: 1}, {a: 0}]

Try this R.descend example in Rambda REPL

All TypeScript definitions
descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
R.descend source
import { createCompareFunction } from './ascend.js'

export function descend(getFunction) {
  return (a, b) => {
    const aValue = getFunction(a)
    const bValue = getFunction(b)

    return createCompareFunction(aValue, bValue, 1, -1)
  }
}

---------------

difference


difference<T>(x: T[]): (y: T[]) => T[]

It returns a merged list of x and y with all equal elements removed.

R.equals is used to determine equality.

const x = [ 1, 2, 3, 4 ]
const y = [ 3, 4, 5, 6 ]

const result = R.difference(x)(y)
// => [ 1, 2, 5, 6 ]

Try this R.difference example in Rambda REPL

All TypeScript definitions
difference<T>(x: T[]): (y: T[]) => T[];
R.difference source
import { filter } from './filter.js'
import { excludes } from './excludes.js'

export function difference(listA) {
	return listB => ([
		...filter(value => excludes(listB)(value))(listA),
		...filter(value => excludes(listA)(value))(listB),
	])
}
Tests
import { difference } from './difference'

test('difference', () => {
  const list1 = [1, 2, 3, 4]
  const list2 = [3, 4, 5, 6]
  expect(difference(list1)(list2)).toEqual([1, 2, 5, 6])
  expect(difference([])([])).toEqual([])
})

test('difference with objects', () => {
  const list1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
  const list2 = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]
  const result = difference(list1)(list2)
  expectTypeOf(result).toEqualTypeOf<{ id: number }[]>()
  expect(result).toEqual([
    { id: 1 },
    { id: 2 },
    { id: 5 },
    { id: 6 },
  ])
})

---------------

drop


drop<T>(howMany: number): (list: T[]) => T[]

It returns howMany items dropped from beginning of list.

R.drop(2)(['foo', 'bar', 'baz']) // => ['baz']

Try this R.drop example in Rambda REPL

All TypeScript definitions
drop<T>(howMany: number): (list: T[]) => T[];
R.drop source
export function drop(howManyToDrop) {
  return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0)
}
Tests
import { drop } from './drop'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([1, 2, 3, 4], drop(2))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([3, 4])
})

test('with array', () => {
  expect(drop(2)(['foo', 'bar', 'baz'])).toEqual(['baz'])
  expect(drop(3)(['foo', 'bar', 'baz'])).toEqual([])
  expect(drop(4)(['foo', 'bar', 'baz'])).toEqual([])
})

test('with non-positive count', () => {
  expect(drop(0)([1, 2, 3])).toEqual([1, 2, 3])
  expect(drop(-1)([1, 2, 3])).toEqual([1, 2, 3])
  expect(drop(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3])
})

---------------

dropLast


dropLast<T>(howMany: number): (list: T[]) => T[]

It returns howMany items dropped from the end of list.

All TypeScript definitions
dropLast<T>(howMany: number): (list: T[]) => T[];
R.dropLast source
export function dropLast(numberItems) {
  return list => (numberItems > 0 ? list.slice(0, -numberItems) : list.slice())
}
Tests
import { dropLast } from './dropLast'

test('with array', () => {
  expect(dropLast(2)(['foo', 'bar', 'baz'])).toEqual(['foo'])
  expect(dropLast(3)(['foo', 'bar', 'baz'])).toEqual([])
  expect(dropLast(4)(['foo', 'bar', 'baz'])).toEqual([])
})

test('with non-positive count', () => {
  expect(dropLast(0)([1, 2, 3])).toEqual([1, 2, 3])
  expect(dropLast(-1)([1, 2, 3])).toEqual([1, 2, 3])
  expect(dropLast(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3])
})

---------------

dropLastWhile


dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]
const list = [1, 2, 3, 4, 5];
const predicate = x => x >= 3

const result = dropLastWhile(predicate)(list);
// => [1, 2]

Try this R.dropLastWhile example in Rambda REPL

All TypeScript definitions
dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropLastWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
R.dropLastWhile source
export function dropLastWhile(predicate) {
  return list => {
    if (list.length === 0) {
      return list
    }

    const toReturn = []
    let counter = list.length

    while (counter) {
      const item = list[--counter]
      if (!predicate(item, counter)) {
        toReturn.push(item)
        break
      }
    }

    while (counter) {
      toReturn.push(list[--counter])
    }

    return toReturn.reverse()
  }
}
Tests
import { dropLastWhile } from './dropLastWhile'

const list = [1, 2, 3, 4, 5]

test('with list', () => {
  const result = dropLastWhile((x: number) => x >= 3)(list)
  expect(result).toEqual([1, 2])
})

test('with empty list', () => {
  expect(dropLastWhile(() => true)([])).toEqual([])
})

---------------

dropRepeatsBy


dropRepeatsBy<T, U>(fn: (x: T) => U): (list: T[]) => T[]
const result = R.dropRepeatsBy(
  Math.abs,
  [1, -1, 2, 3, -3]
)
// => [1, 2, 3]

Try this R.dropRepeatsBy example in Rambda REPL

All TypeScript definitions
dropRepeatsBy<T, U>(fn: (x: T) => U): (list: T[]) => T[];

---------------

dropRepeatsWith


dropRepeatsWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[]
const list = [{a:1,b:2}, {a:1,b:3}, {a:2, b:4}]
const result = R.dropRepeatsWith(R.prop('a'))(list)

// => [{a:1,b:2}, {a:2, b:4}]

Try this R.dropRepeatsWith example in Rambda REPL

All TypeScript definitions
dropRepeatsWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[];

---------------

dropWhile


dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]
const list = [1, 2, 3, 4]
const predicate = x => x < 3
const result = R.dropWhile(predicate)(list)
// => [3, 4]

Try this R.dropWhile example in Rambda REPL

All TypeScript definitions
dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
R.dropWhile source
export function dropWhile(predicate) {
  return iterable => {
    const toReturn = []
    let counter = 0

    while (counter < iterable.length) {
      const item = iterable[counter++]
      if (!predicate(item, counter)) {
        toReturn.push(item)
        break
      }
    }

    while (counter < iterable.length) {
      toReturn.push(iterable[counter++])
    }

    return toReturn
  }
}
Tests
import { dropWhile } from './dropWhile'
import { pipe } from './pipe'

const list = [1, 2, 3, 4]

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    dropWhile((x: number) => x < 3),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([3])
})

test('always false', () => {
  const predicate = () => false
  const result = dropWhile(predicate)(list)
  expect(result).toEqual(list)
})

test('with index', () => {
  const result = pipe(
    [1, 2, 3],
    dropWhile((x: number, i: number) => {
      expectTypeOf(i).toEqualTypeOf<number>()
      return x + i > 2
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 2, 3])
})

---------------

duplicateBy


duplicateBy<T, U>(fn: (x: T) => U): (list: T[]) => T[]
const list = [{a:1}, {a:2}, {a:1}]
const result = R.duplicateBy(x => x)(list)

// => [{a:1}]

Try this R.duplicateBy example in Rambda REPL

All TypeScript definitions
duplicateBy<T, U>(fn: (x: T) => U): (list: T[]) => T[];
R.duplicateBy source
import { _Set } from '../src/_internals/set.js'

export function duplicateBy(fn) {
  return list => {
    const set = new _Set()

    return list.filter(item => !set.checkUniqueness(fn(item)))
  }
}
Tests
import { duplicateBy } from './duplicateBy'

test('happy', () => {
  expect(duplicateBy(Math.abs)([-2, -1, 0, 1, 2])).toEqual([1, 2])
})

test('returns an empty array for an empty array', () => {
  expect(duplicateBy(Math.abs)([])).toEqual([])
})

test('uses R.uniq', () => {
  const list = [{ a: 1 }, { a: 2 }, { a: 1 }]
  const expected = [{ a: 1 }]
  expect(duplicateBy((x: any) => x)(list)).toEqual(expected)
})

---------------

eqBy


eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean
const result = R.eqBy(Math.abs, 5)(-5)
// => true

Try this R.eqBy example in Rambda REPL

All TypeScript definitions
eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean;
R.eqBy source
import { equalsFn } from './equals.js'

export function eqBy(fn, a) {
  return b => equalsFn(fn(a), fn(b))
}
Tests
import { eqBy } from './eqBy'

test('deteremines whether two values map to the same value in the codomain', () => {
  expect(eqBy<number>(Math.abs, 5)(5)).toBe(true)
  expect(eqBy<number>(Math.abs, 5)(-5)).toBe(true)
  expect(eqBy<number>(Math.abs, -5)(5)).toBe(true)
  expect(eqBy<number>(Math.abs, -5)(-5)).toBe(true)
  expect(eqBy<number>(Math.abs, 42)(99)).toBe(false)
})

test('has R.equals semantics', () => {
  expect(eqBy(Math.abs, Number.NaN)(Number.NaN)).toBe(true)
  expect(eqBy((x: number[]) => x.length, [42])([42])).toBe(true)
  expect(eqBy((x: any) => x, { a: 1 })({ a: 1 })).toBe(true)
  expect(eqBy((x: any) => x, { a: 1 })({ a: 2 })).toBe(false)
})

---------------

eqProps


eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean

It returns true if property prop in obj1 is equal to property prop in obj2 according to R.equals.

const obj1 = {a: 1, b:2}
const obj2 = {a: 1, b:3}
const result = R.eqProps('a', obj1)(obj2)
// => true

Try this R.eqProps example in Rambda REPL

All TypeScript definitions
eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean;
R.eqProps source
import { equalsFn } from './equals.js'

export function eqProps(property, objA) {
  return objB => equalsFn( objA[property], objB[property] )
}
Tests
import { eqProps } from './eqProps'
import { pipe } from './pipe'

const obj1 = {
  a: 1,
  b: 2,
}
const obj2 = {
  a: 1,
  b: 3,
}

test('happy', () => {
  const objA = { a: { b: 1 }, c: 2 }
  const objB = { a: { b: 1 }, c: 3 }
  const result = pipe(objA, eqProps('a', objB))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('props are equal', () => {
  const result = eqProps('a', obj1)(obj2)
  expect(result).toBeTruthy()
})

test('props are not equal', () => {
  const result = eqProps('b', obj1)(obj2)
  expect(result).toBeFalsy()
})

test('prop does not exist', () => {
  const result = eqProps('c' as 'a' | 'b', obj1)(obj2)
  expect(result).toBeTruthy()
})

---------------

equals


equals<T>(x: T): (y: T) => boolean

It deeply compares x and y and returns true if they are equal.

💥 It doesn't handle cyclical data structures and functions

R.equals(
  [1, {a:2}, [{b: 3}]],
  [1, {a:2}, [{b: 3}]]
) // => true

Try this R.equals example in Rambda REPL

All TypeScript definitions
equals<T>(x: T): (y: T) => boolean;
R.equals source
import { isArray } from './_internals/isArray.js'
import { type } from './type.js'

export function _lastIndexOf(valueToFind, list) {
  if (!isArray(list)) {
    throw new Error(`Cannot read property 'indexOf' of ${list}`)
  }

  const typeOfValue = type(valueToFind)
  if (!['Array', 'NaN', 'Object', 'RegExp'].includes(typeOfValue)) {
    return list.lastIndexOf(valueToFind)
  }

  const { length } = list
  let index = length
  let foundIndex = -1

  while (--index > -1 && foundIndex === -1) {
    if (equalsFn(list[index], valueToFind)) {
      foundIndex = index
    }
  }

  return foundIndex
}

export function _indexOf(valueToFind, list) {
  if (!isArray(list)) {
    throw new Error(`Cannot read property 'indexOf' of ${list}`)
  }

  const typeOfValue = type(valueToFind)
  if (!['Array', 'NaN', 'Object', 'RegExp'].includes(typeOfValue)) {
    return list.indexOf(valueToFind)
  }

  let index = -1
  let foundIndex = -1
  const { length } = list

  while (++index < length && foundIndex === -1) {
    if (equalsFn(list[index], valueToFind)) {
      foundIndex = index
    }
  }

  return foundIndex
}

function _arrayFromIterator(iter) {
  const list = []
  let next
  while (!(next = iter.next()).done) {
    list.push(next.value)
  }

  return list
}

function _compareSets(a, b) {
  if (a.size !== b.size) {
    return false
  }

  const aList = _arrayFromIterator(a.values())
  const bList = _arrayFromIterator(b.values())

  const filtered = aList.filter(aInstance => _indexOf(aInstance, bList) === -1)

  return filtered.length === 0
}

function compareErrors(a, b) {
  if (a.message !== b.message) {
    return false
  }
  if (a.toString !== b.toString) {
    return false
  }

  return a.toString() === b.toString()
}

function parseDate(maybeDate) {
  if (!maybeDate.toDateString) {
    return [false]
  }

  return [true, maybeDate.getTime()]
}

function parseRegex(maybeRegex) {
  if (maybeRegex.constructor !== RegExp) {
    return [false]
  }

  return [true, maybeRegex.toString()]
}

export function equalsFn(a, b) {
  if (Object.is(a, b)) {
    return true
  }

  const aType = type(a)

  if (aType !== type(b)) {
    return false
  }
  if (aType === 'Function') {
    return a.name === undefined ? false : a.name === b.name
  }

  if (['NaN', 'Null', 'Undefined'].includes(aType)) {
    return true
  }

  if (['BigInt', 'Number'].includes(aType)) {
    if (Object.is(-0, a) !== Object.is(-0, b)) {
      return false
    }

    return a.toString() === b.toString()
  }

  if (['Boolean', 'String'].includes(aType)) {
    return a.toString() === b.toString()
  }

  if (aType === 'Array') {
    const aClone = Array.from(a)
    const bClone = Array.from(b)

    if (aClone.toString() !== bClone.toString()) {
      return false
    }

    let loopArrayFlag = true
    aClone.forEach((aCloneInstance, aCloneIndex) => {
      if (loopArrayFlag) {
        if (
          aCloneInstance !== bClone[aCloneIndex] &&
          !equalsFn(aCloneInstance, bClone[aCloneIndex])
        ) {
          loopArrayFlag = false
        }
      }
    })

    return loopArrayFlag
  }

  const aRegex = parseRegex(a)
  const bRegex = parseRegex(b)

  if (aRegex[0]) {
    return bRegex[0] ? aRegex[1] === bRegex[1] : false
  }
  if (bRegex[0]) {
    return false
  }

  const aDate = parseDate(a)
  const bDate = parseDate(b)

  if (aDate[0]) {
    return bDate[0] ? aDate[1] === bDate[1] : false
  }
  if (bDate[0]) {
    return false
  }

  if (a instanceof Error) {
    if (!(b instanceof Error)) {
      return false
    }

    return compareErrors(a, b)
  }

  if (aType === 'Set') {
    return _compareSets(a, b)
  }

  if (aType === 'Object') {
    const aKeys = Object.keys(a)

    if (aKeys.length !== Object.keys(b).length) {
      return false
    }

    let loopObjectFlag = true
    aKeys.forEach(aKeyInstance => {
      if (loopObjectFlag) {
        const aValue = a[aKeyInstance]
        const bValue = b[aKeyInstance]

        if (aValue !== bValue && !equalsFn(aValue, bValue)) {
          loopObjectFlag = false
        }
      }
    })

    return loopObjectFlag
  }

  return false
}
export function equals(a) {
  return b => equalsFn(a, b)
}
Tests
import { equals } from './equals'

test('happy', () => {
  const result = equals(4)(1)
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(false)

  const foo = { a: 1 }
  const bar = { a: 2 }
  const result2 = equals(foo)(bar)
  expectTypeOf(result2).toEqualTypeOf<boolean>()
  expect(result2).toBe(false)
})

test('compare functions', () => {
  function foo() {}
  function bar() {}
  const baz = () => {}

  const expectTrue = equals(foo)(foo)
  const expectFalseFirst = equals(bar)(foo)
  const expectFalseSecond = equals(baz)(foo)

  expect(expectTrue).toBeTruthy()
  expect(expectFalseFirst).toBeFalsy()
  expect(expectFalseSecond).toBeFalsy()
})

test('with array of objects', () => {
  const list1 = [{ a: 1 }, [{ b: 2 }]]
  const list2 = [{ a: 1 }, [{ b: 2 }]]
  const list3 = [{ a: 1 }, [{ b: 3 }]]

  expect(equals(list1)(list2)).toBeTruthy()
  expect(equals(list1)(list3)).toBeFalsy()
})

test('with regex', () => {
  expect(equals(/s/)(/s/)).toBeTruthy()
  expect(equals(/s/)(/d/)).toBeFalsy()
  expect(equals(/a/gi)(/a/gi)).toBeTruthy()
  expect(equals(/a/gim)(/a/gim)).toBeTruthy()
  expect(equals(/a/gi)(/a/i)).toBeFalsy()
})

test('not a number', () => {
  expect(equals([Number.NaN])([Number.NaN])).toBeTruthy()
})

test('new number', () => {
  expect(equals(new Number(0))(new Number(0))).toBeTruthy()
  expect(equals(new Number(0))(new Number(1))).toBeFalsy()
  expect(equals(new Number(1))(new Number(0))).toBeFalsy()
})

test('new string', () => {
  expect(equals(new String(''))(new String(''))).toBeTruthy()
  expect(equals(new String(''))(new String('x'))).toBeFalsy()
  expect(equals(new String('x'))(new String(''))).toBeFalsy()
  expect(equals(new String('foo'))(new String('foo'))).toBeTruthy()
  expect(equals(new String('foo'))(new String('bar'))).toBeFalsy()
  expect(equals(new String('bar'))(new String('foo'))).toBeFalsy()
})

test('new Boolean', () => {
  expect(equals(new Boolean(true))(new Boolean(true))).toBeTruthy()
  expect(equals(new Boolean(false))(new Boolean(false))).toBeTruthy()
  expect(equals(new Boolean(true))(new Boolean(false))).toBeFalsy()
  expect(equals(new Boolean(false))(new Boolean(true))).toBeFalsy()
})

test('new Error', () => {
  expect(equals(new Error('XXX'))({} as any)).toBeFalsy()
  expect(equals(new Error('XXX'))(new TypeError('XXX'))).toBeFalsy()
  expect(equals(new Error('XXX'))(new Error('YYY'))).toBeFalsy()
  expect(equals(new Error('XXX'))(new Error('XXX'))).toBeTruthy()
  expect(equals(new Error('XXX'))(new TypeError('YYY'))).toBeFalsy()
})

test('with dates', () => {
  expect(equals(new Date(0))(new Date(0))).toBeTruthy()
  expect(equals(new Date(1))(new Date(1))).toBeTruthy()
  expect(equals(new Date(0))(new Date(1))).toBeFalsy()
  expect(equals(new Date(1))(new Date(0))).toBeFalsy()
  expect(equals(new Date(0))({} as any)).toBeFalsy()
  expect(equals({})(new Date(0))).toBeFalsy()
})

test('ramda spec', () => {
  expect(equals({})({})).toBeTruthy()

  expect(
    equals(
      {
        a: 1,
        b: 2,
      })({
        a: 1,
        b: 2,
      }),
     )
  .toBeTruthy()

  expect(
    equals(
      {
        a: 2,
        b: 3,
      })(
      {
        a: 2,
        b: 3,
      } as any
    )
  ).toBeTruthy()

  expect(
    equals(
      {
        a: 2,
        b: 3,
      })(
      {
        a: 3,
        b: 3,
      } as any
    ),
  ).toBeFalsy()

  expect(
    equals(
      {
        a: 2,
        b: 3,
        c: 1,
      })(
      {
        a: 2,
        b: 3,
      } as any
    ))
  .toBeFalsy()
})

test('works with boolean tuple', () => {
  expect(equals([true, false])([true, false])).toBeTruthy()
  expect(equals([true, false])([true, true])).toBeFalsy()
})

test('works with equal objects within array', () => {
  const objFirst = {
    a: {
      b: 1,
      c: 2,
      d: [1],
    },
  }
  const objSecond = {
    a: {
      b: 1,
      c: 2,
      d: [1],
    },
  }

  const x = [1, 2, objFirst, null, '', []]
  const y = [1, 2, objSecond, null, '', []]
  expect(equals(x)(y)).toBeTruthy()
})

test('works with different objects within array', () => {
  const objFirst = { a: { b: 1 } }
  const objSecond = { a: { b: 2 } }

  const x = [1, 2, objFirst, null, '', []]
  const y = [1, 2, objSecond, null, '', []]
  expect(equals(x)(y)).toBeFalsy()
})

test('works with undefined as second argument', () => {
  expect(equals(1)(undefined as any)).toBeFalsy()
  expect(equals(undefined)(undefined)).toBeTruthy()
})

test('compare sets', () => {
  const toCompareDifferent = new Set([{ a: 1 }, { a: 2 }])
  const toCompareSame = new Set([{ a: 1 }, { a: 2 }, { a: 1 }])
  const testSet = new Set([{ a: 1 }, { a: 2 }, { a: 1 }])
  expect(equals(toCompareSame)(testSet)).toBeTruthy()
  expect(equals(toCompareDifferent)(testSet)).toBeFalsy()
})

test('compare simple sets', () => {
  const testSet = new Set(['2', '3', '3', '2', '1'])
  expect(equals(new Set(['3', '2', '1']))(testSet)).toBeTruthy()
  expect(equals(new Set(['3', '2', '0']))(testSet)).toBeFalsy()
})

test('various examples', () => {
  expect(equals([1, 2, 3])([1, 2, 3])).toBeTruthy()
  expect(equals([1, 2, 3])([1, 2])).toBeFalsy()
  expect(equals({})({})).toBeTruthy()
})

---------------

evolve


evolve<T>(rules: {
	[K in keyof T]?: (x: T[K]) => T[K]
}): (obj: T) => T

It takes object of functions as set of rules. These rules are applied to the iterable input to produce the result. It doesn't support nested rules, i.e rules are only one level deep.

const input = {
	foo: 2,
	baz: 'baz',
}
const result = R.pipe(
	input,
	R.evolve({
		foo: x => x + 1,
	})
)
// => result is { foo: 3, baz: 'baz' }

Try this R.evolve example in Rambda REPL

All TypeScript definitions
evolve<T>(rules: {
	[K in keyof T]?: (x: T[K]) => T[K]
}): (obj: T) => T;
R.evolve source
import { mapObject } from './mapObject.js'
import { type } from './type.js'

export function evolve(rules) {
  return mapObject((x, prop) => type(rules[prop]) === 'Function' ? rules[prop](x): x)
}
Tests
import { evolve } from './evolve'
import { pipe } from './pipe'

test('happy', () => {
  const input = {
    baz: 1,
    foo: 2,
    nested: {
      a: 1,
      bar: 3,
    },
  }
  const result = pipe(input,
    evolve({
      foo: (x) => x + 1,
    })
  )
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expectTypeOf(result.baz).toEqualTypeOf<number>()
  expectTypeOf(result.nested.a).toEqualTypeOf<number>()
  expect(result).toEqual({
    baz: 1,
    foo: 3,
    nested: { a: 1, bar: 3 },
  })
})

---------------

excludes


excludes(list: readonly string[] | string): (substringToFind: string) => boolean

Opposite of R.includes

R.equals is used to determine equality.

const result = [
  R.excludes('foo')('ar'),
  R.excludes([{a: 1}])({a: 2})
]
// => [true, true ]

Try this R.excludes example in Rambda REPL

All TypeScript definitions
excludes(list: readonly string[] | string): (substringToFind: string) => boolean;
excludes<T>(list: readonly T[]): (target: T) => boolean;
R.excludes source
import { includes } from './includes.js'

export function excludes(iterable) {
  return valueToFind => !includes(iterable)(valueToFind)
}
Tests
import { excludes } from './excludes'
import { pipe } from './pipe'

test('happy', () => {
  const list = [{ a: { b: '1' } }, { a: { b: '2' } }, { a: { b: '3' } }]
  const result = pipe({ a: { b: '1' } }, excludes(list))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(false)

  const stringResult = pipe('foo', excludes('bar'))
  expectTypeOf(stringResult).toEqualTypeOf<boolean>()
  expect(stringResult).toBe(true)
})

test('excludes with string', () => {
  const str = 'more is less'
  expect(excludes(str)('less')).toBeFalsy()
  expect(excludes(str)('never')).toBeTruthy()
})

test('excludes with array', () => {
  const arr = [1, 2, 3]
  expect(excludes(arr)(2)).toBeFalsy()
  expect(excludes(arr)(4)).toBeTruthy()
})

---------------

exists


exists<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true if there is at least one element in list that satisfy the predicate.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 'bar'}, {foo: 1}]

const result = R.exists(predicate)(list)
// => true

Try this R.exists example in Rambda REPL

All TypeScript definitions
exists<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.exists source
import { find } from './find.js'

export function exists(predicate) {
  return list => {
		return find(predicate)(list) !== undefined
  }
}
Tests
import { exists } from './exists'
import { propEq } from './propEq'
import { pipe } from './pipe'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }]

test('happy', () => {
  const predicate = (x: number) => x > 2
  const result = pipe([1, 2, 3], exists(predicate))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBe(true)
})

test('nothing is found', () => {
  const fn = propEq(4, 'a')
  expect(exists(fn)(list)).toBe(false)
})

---------------

filter


filter<T, S extends T>(
  predicate: (value: T) => value is S,
): (list: T[]) => S[]

It filters list or object input using a predicate function.

const predicate = x => x > 1
const list = [1, 2, 3]
const result = R.filter(predicate)(list)
// => [2, 3]

Try this R.filter example in Rambda REPL

All TypeScript definitions
filter<T, S extends T>(
  predicate: (value: T) => value is S,
): (list: T[]) => S[];
filter<T>(
	predicate: BooleanConstructor,
): (list: readonly T[]) => ExcludeFalsy<T>[];
filter<T>(
	predicate: BooleanConstructor,
): (list: T[]) => ExcludeFalsy<T>[];
filter<T>(
	predicate: (value: T, index: number) => boolean,
): (list: T[]) => T[];
...
...
R.filter source
export function filter(predicate) {
  return list => {
    if (!list) {
      throw new Error('Incorrect iterable input')
    }
    let index = 0
    const len = list.length
    const willReturn = []

    while (index < len) {
      if (predicate(list[index], index)) {
        willReturn.push(list[index])
      }

      index++
    }

    return willReturn
  }
}
Tests
import { filter } from './filter'
import { includes } from './includes'
import { pipe } from './pipe'
import { reject } from './reject'
import { sort } from './sort'
import { split } from './split'
import { uniq } from './uniq'

test('happy', () => {
  const isEven = (n: number) => n % 2 === 0
  expect(filter(isEven)([1, 2, 3, 4])).toEqual([2, 4])
})

test('using Boolean', () => {
  expect(filter(Boolean)([null, 0, 1, 2])).toEqual([1, 2])
})

test('within pipe', () => {
  const result = pipe(
    [1, 2, 3],
    filter((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([2, 3])
})

test('with index', () => {
  const result = pipe(
    [1, 2, 3],
    filter((x: number, i: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      expectTypeOf(i).toEqualTypeOf<number>()
      return x > 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([2, 3])
})

test('complex example', () => {
  const text = `Dies ist ein einfacher Beispielsatz. Il fait beau aujourd'hui!`
  const language = 'de'
  const SENTENCE_END_CHARS = ['.', '!', '?', '।', '؟']
  const result = pipe(
    text,
    split(''),
    uniq,
    filter((char: string) => {
      if (language === 'de') {
        return /[A-Za-zäßüöÜÖÄ]/g.test(char) === false
      }
      if (language === 'fr') {
        return /[A-Za-zÀÉàâçèéêîïôùû']/g.test(char) === false
      }
      throw new Error(`Language ${language} not supported`)
    }),
    sort((a: string, b: string) => (a === b ? 0 : a > b ? 1 : -1)),
    filter((char: string) => char.trim().length > 0),
    reject(includes(SENTENCE_END_CHARS)),
  )
  expectTypeOf(result).toEqualTypeOf<string[]>()
})

test('narrowing type', () => {
  interface Foo {
    a: number
  }
  interface Bar extends Foo {
    b: string
  }
  type T = Foo | Bar
  const testList: T[] = [{ a: 1 }, { a: 2 }, { a: 3 }]
  const filterBar = (x: T): x is Bar => {
    return typeof (x as Bar).b === 'string'
  }
  const result = pipe(testList, filter(filterBar))
  expectTypeOf(result).toEqualTypeOf<Bar[]>()
})

test('narrowing type - readonly', () => {
  interface Foo {
    a: number
  }
  interface Bar extends Foo {
    b: string
  }
  type T = Foo | Bar
  const testList: T[] = [{ a: 1 }, { a: 2 }, { a: 3 }] as const
  const filterBar = (x: T): x is Bar => {
    return typeof (x as Bar).b === 'string'
  }
  const result = pipe(testList, filter(filterBar))
  expectTypeOf(result).toEqualTypeOf<Bar[]>()
})

test('filtering NonNullable - list of objects', () => {
  const testList = [{ a: 1 }, { a: 2 }, false, { a: 3 }]
  const result = pipe(testList, filter(Boolean))
  expectTypeOf(result).toEqualTypeOf<{ a: number }[]>()
})

test('filtering NonNullable - readonly', () => {
  const testList = [1, 2, true, false, null, undefined, 3] as const
  const result = pipe(testList, filter(Boolean))
  expectTypeOf(result).toEqualTypeOf<(1 | 2 | 3)[]>()
})

---------------

filterAsync


filterAsync<T>(
	predicate: (value: T) => Promise<boolean>,
): (list: T[]) => Promise<T[]>
All TypeScript definitions
filterAsync<T>(
	predicate: (value: T) => Promise<boolean>,
): (list: T[]) => Promise<T[]>;
R.filterAsync source
export function filterAsync(predicate) {
  return async list => {
    const willReturn = []
    let index = 0
    for (const x of list) {
      if (await predicate(x, index)) {
        willReturn.push(list[index])
      }
      index++
    }

    return willReturn
  }
}
Tests
import { filterAsync } from './filterAsync'
import { pipeAsync } from './pipeAsync'

test('happy', async () => {
  const isEven = async (n: number) => n % 2 === 0
  expect(await filterAsync(isEven)([1, 2, 3, 4])).toEqual([2, 4])
})

test('within pipe', async () => {
  const result = await pipeAsync(
    [1, 2, 3],
    filterAsync(async (x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return x > 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([2, 3])
})

---------------

filterMap


filterMap<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, ExcludeFalsy<U>>

Same as R.map but it filters out null/undefined if returned from functor functions.

💥 This function doesn't work with objects (use R.mapObject instead)

const result = R.pipe(
	[1, 2, 3],
	R.filterMap(x => x > 1 ? x : null)
)
// => [2, 3]

Try this R.filterMap example in Rambda REPL

All TypeScript definitions
filterMap<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, ExcludeFalsy<U>>;
filterMap<T extends IterableContainer, U>(
	fn: (value: T[number]) => U,
): (data: T) => Mapped<T, ExcludeFalsy<U>>;
R.filterMap source
import {mapFn} from './map.js'

export function filterMap(fn) {
  return list => mapFn(fn, list).filter(Boolean)
}
Tests
import { filterMap } from './filterMap'
import { pipe } from './pipe'

const double = (x: number) => x > 1 ? x * 2 : null

test('happy', () => {
  expect(filterMap<number[], number | null>(double)([1, 2, 3])).toEqual([4, 6])
})

test('within pipe', () => {
  const result = pipe(
    [1, 2, 3],
    (x: number[]) => x,
    filterMap((x: number) => {
      expectTypeOf(x).toEqualTypeOf<number>()
      return Math.random() > 0.5 ? String(x) : null
    }),
    filterMap((x: string) => {
      expectTypeOf(x).toEqualTypeOf<string>()
      return Math.random() > 0.5 ? Number(x) : ''
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
})

---------------

filterObject


filterObject<T extends object>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => boolean,
): <U extends T>(data: T) => U

It loops over each property of obj and returns a new object with only those properties that satisfy the predicate.

const result = R.filterObject(
	(val, prop) => prop === 'a' || val > 1
)({a: 1, b: 2, c:3})
// => {a: 1, c: 3}

Try this R.filterObject example in Rambda REPL

All TypeScript definitions
filterObject<T extends object>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => boolean,
): <U extends T>(data: T) => U;
R.filterObject source
export function filterObject(predicate) {
  return obj => {
    const willReturn = {}

    for (const prop in obj) {
      if (predicate(obj[prop], prop, obj)) {
        willReturn[prop] = obj[prop]
      }
    }

    return willReturn
  }
}
Tests
import { filterObject } from './filterObject'
import { pipe } from './pipe'

test('happy', () => {
  const testInput = { a: 1, b: 2, c: 3 }
  const result = pipe(
    testInput,
    filterObject((x: number, prop: string, obj: typeof testInput) => {
      expect(prop).toBeOneOf(['a', 'b', 'c'])
      expect(obj).toBe(testInput)
      return x > 1
    })
  )
  expect(result).toEqual({ b: 2, c: 3 })
})

test('require explicit type', () => {
  const result = pipe(
    { a: 1, b: 2 },
    filterObject<{ b: number }>((a: number) => {
      expectTypeOf(a).toEqualTypeOf<number>()
      return a > 1
    }),
  )
  expectTypeOf(result.b).toEqualTypeOf<number>()
  expect(result).toEqual({ b: 2 })
})

---------------

find


find<T, S extends T>(predicate: (x: T) => x is S): (list: T[]) => S | undefined

It returns the first element of list that satisfy the predicate.

If there is no such element, it returns undefined.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 'bar'}, {foo: 1}]

const result = R.find(predicate)(list)
// => {foo: 1}

Try this R.find example in Rambda REPL

All TypeScript definitions
find<T, S extends T>(predicate: (x: T) => x is S): (list: T[]) => S | undefined;
find<T>(predicate: (x: T) => boolean): (list: T[]) => T | undefined;
R.find source
export function find(predicate) {
  return list => {
    let index = 0
    const len = list.length

    while (index < len) {
      const x = list[index]
      if (predicate(x)) {
        return x
      }

      index++
    }
  }
}
Tests
import { find } from './find'
import { propEq } from './propEq'
import { pipe } from './pipe'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }]

test('happy', () => {
  const fn = propEq(2, 'a')
  const result = find(fn)(list)
  expectTypeOf(result).toEqualTypeOf<{ a: number } | undefined>()
  expect(result).toEqual({ a: 2 })
})

test('nothing is found', () => {
  const fn = propEq(4, 'a')
  expect(find(fn)(list)).toBeUndefined()
})

test('with empty list', () => {
  expect(find(() => true)([])).toBeUndefined()
})

test('has type guard narrowing', () => {
  const items = ['hello', 'world', 42] as (string | number)[]
  const result = pipe(
    items,
    find((x): x is string => typeof x === 'string'),
  )
  expectTypeOf(result).toEqualTypeOf<string | undefined>()
  expect(result).toBe('hello')
})

---------------

findIndex


findIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number

It returns the index of the first element of list satisfying the predicate function.

If there is no such element, then -1 is returned.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 'bar'}, {foo: 1}]

const result = R.findIndex(predicate)(list)
// => 1

Try this R.findIndex example in Rambda REPL

All TypeScript definitions
findIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
R.findIndex source
export function findIndex(predicate) {
  return list => {
    const len = list.length
    let index = -1

    while (++index < len) {
      if (predicate(list[index])) {
        return index
      }
    }

    return -1
  }
}
Tests
import { findIndex } from './findIndex'
import { propEq } from './propEq'
import { pipe } from './pipe'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }]

test('happy', () => {
  expect(findIndex(propEq(2, 'a'))(list)).toBe(1)
  expect(findIndex(propEq(1, 'a'))(list)).toBe(0)
  expect(findIndex(propEq(4, 'a'))(list)).toBe(-1)
})

test('type test', () => {
  const result = pipe(
    [1, 2, 3],
    findIndex((x: number) => x > 2),
  )
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

---------------

findLast


findLast<T>(fn: (x: T) => boolean): (list: T[]) => T | undefined

It returns the last element of list satisfying the predicate function.

If there is no such element, then undefined is returned.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 0}, {foo: 1}]

const result = R.findLast(predicate)(list)
// => {foo: 1}

Try this R.findLast example in Rambda REPL

All TypeScript definitions
findLast<T>(fn: (x: T) => boolean): (list: T[]) => T | undefined;
R.findLast source
export function findLast(predicate) {
  return list => {
    let index = list.length

    while (--index >= 0) {
      if (predicate(list[index])) {
        return list[index]
      }
    }

    return undefined
  }
}

---------------

findLastIndex


findLastIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number

It returns the index of the last element of list satisfying the predicate function.

If there is no such element, then -1 is returned.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 0}, {foo: 1}]

const result = R.findLastIndex(predicate)(list)
// => 1

Try this R.findLastIndex example in Rambda REPL

All TypeScript definitions
findLastIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
R.findLastIndex source
export function findLastIndex(fn) {
  return list => {
    let index = list.length

    while (--index >= 0) {
      if (fn(list[index])) {
        return index
      }
    }

    return -1
  }
}
Tests
import { findLastIndex } from './findLastIndex'

test('happy', () => {
  const result = findLastIndex((x: number) => x > 1)([1, 1, 1, 2, 3, 4, 1])
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(5)
  expect(findLastIndex((x: number) => x === 0)([0, 1, 1, 2, 3, 4, 1])).toBe(0)
})

---------------

findNth


findNth<T>(predicate: (x: T) => boolean, nth: number): (list: T[]) => T | undefined

It returns the nth element of list that satisfy the predicate function.

const predicate = x => R.type(x.foo) === 'Number'
const list = [{foo: 0}, {foo: 1}, {foo: 2}, {foo: 3}]

const result = R.findNth(predicate, 2)(list)
// => {foo: 2}

Try this R.findNth example in Rambda REPL

All TypeScript definitions
findNth<T>(predicate: (x: T) => boolean, nth: number): (list: T[]) => T | undefined;
R.findNth source
export function findNth(predicate, nth) {
  return list => {
    let index = 0
    const len = list.length

    while (index < len) {
      const x = list[index]
      if (predicate(x)) {
				if (nth === 0) return x
				nth--
      }

      index++
    }
  }
}
Tests
import { findNth } from './findNth'

const list = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]

test('happy', () => {
  const fn = (x: { a: number }) => x.a > 1
  expect(findNth(fn, 1)(list)).toEqual({ a: 3 })
})

test('nothing is found', () => {
  const fn = (x: { a: number }) => x.a > 4
  expect(findNth(fn, 1)(list)).toBeUndefined()
})

---------------

flatMap


flatMap<T, U extends unknown>(transformFn: (x: T extends any[] ? T[number]: never) => U): (listOfLists: T[]) => U[]

It maps fn over list and then flatten the result by one-level.

const duplicate = n => [ n, n ]
const list = [ 1, 2, 3 ]

const result = R.flatMap(duplicate)(list)
// => [ 1, 1, 2, 2, 3, 3 ]

Try this R.flatMap example in Rambda REPL

All TypeScript definitions
flatMap<T, U extends unknown>(transformFn: (x: T extends any[] ? T[number]: never) => U): (listOfLists: T[]) => U[];
R.flatMap source
export function flatMap(fn) {
  return list => [].concat(...list.map(fn))
}
Tests
import { flatMap } from './flatMap'
import { pipe } from './pipe'

const duplicate = (n: number) => [n, n]

test('happy', () => {
  const listOfLists: string[][] = [
    ['f', 'bar'],
    ['baz', 'b'],
  ]
  const result = pipe(
    listOfLists,
    (x: string[][]) => x,
    flatMap((x: string) => {
      expectTypeOf(x).toEqualTypeOf<string>()
      return Number(x) + 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
})

test('maps then flattens one level', () => {
  expect(flatMap(duplicate)([1, 2, 3])).toEqual([1, 1, 2, 2, 3, 3])
})

test('flattens only one level', () => {
  const nest = (n: number) => [[n]]
  expect(flatMap(nest)([1, 2, 3])).toEqual([[1], [2], [3]])
})

test('can compose', () => {
  function dec(x: number) {
    return [x - 1]
  }
  function times2(x: number) {
    return [x * 2]
  }
  const mdouble = flatMap(times2)
  const mdec = flatMap(dec)
  expect(mdec(mdouble([10, 20, 30]))).toEqual([19, 39, 59])
})

---------------

flatten


flatten<T>(list: any[]): T[]

It deeply flattens an array. You must pass expected output type as a type argument.

const result = R.flatten<number>([
  1,
  2,
  [3, 30, [300]],
  [4]
])
// => [ 1, 2, 3, 30, 300, 4 ]

Try this R.flatten example in Rambda REPL

All TypeScript definitions
flatten<T>(list: any[]): T[];
R.flatten source
import { isArray } from './_internals/isArray.js'

export function flatten(list, input) {
  const willReturn = input === undefined ? [] : input

  for (let i = 0; i < list.length; i++) {
    if (isArray(list[i])) {
      flatten(list[i], willReturn)
    } else {
      willReturn.push(list[i])
    }
  }

  return willReturn
}
Tests
import { flatten } from './flatten'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([1, 2, [3, [4]]], flatten<number>)
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 2, 3, 4])
})

---------------

flattenObject


flattenObject<T extends object>(obj: T): FlattenObject<T>

It transforms object to object where each value is represented with its path.

const result = R.flattenObject(
	[1, 2, 3]
)
// => [3, 1, 2] or [2, 3, 1] or ...

Try this R.flattenObject example in Rambda REPL

All TypeScript definitions
flattenObject<T extends object>(obj: T): FlattenObject<T>;
R.flattenObject source
import { type } from './type.js'

export function flattenObjectHelper(obj, accumulator = []){
  const willReturn = {}
  Object.keys(obj).forEach(key => {
    const typeIs = type(obj[ key ])
    if (typeIs === 'Object'){
      const [ flatResultValue, flatResultPath ] = flattenObjectHelper(obj[ key ],
        [ ...accumulator, key ])
      willReturn[ flatResultPath.join('.') ] = flatResultValue

      return
    } else if (accumulator.length > 0){
      const finalKey = [ ...accumulator, key ].join('.')
      willReturn[ finalKey ] = obj[ key ]

      return
    }
    willReturn[ key ] = obj[ key ]
  })
  if (accumulator.length > 0) return [ willReturn, accumulator ]

  return willReturn
}

export function transformFlatObject(obj){
  const willReturn = {}

  const transformFlatObjectFn = objLocal => {
    const willReturnLocal = {}
    Object.keys(objLocal).forEach(key => {
      const typeIs = type(objLocal[ key ])
      if (typeIs === 'Object'){
        transformFlatObjectFn(objLocal[ key ])

        return
      }
      willReturnLocal[ key ] = objLocal[ key ]
      willReturn[ key ] = objLocal[ key ]
    })

    return willReturnLocal
  }

  Object.keys(obj).forEach(key => {
    const typeIs = type(obj[ key ])
    if (typeIs === 'Object'){
      transformFlatObjectFn(obj[ key ], key)

      return
    }
    willReturn[ key ] = obj[ key ]
  })

  return willReturn
}

export function flattenObject(obj){
  const willReturn = {}

  Object.keys(obj).forEach(key => {
    const typeIs = type(obj[ key ])
    if (typeIs === 'Object'){
      const flatObject = flattenObjectHelper(obj[ key ])
      const transformed = transformFlatObject(flatObject)

      Object.keys(transformed).forEach(keyTransformed => {
        willReturn[ `${ key }.${ keyTransformed }` ] = transformed[ keyTransformed ]
      })
    } else {
      willReturn[ key ] = obj[ key ]
    }
  })

  return willReturn
}
Tests
import { flattenObject } from './flattenObject'

test('happy', () => {
  const result = flattenObject({
    a: 1,
    b: {
      c: 3,
      d: {
        e: 5,
        z: 4,
        f: {
          h: 6,
          i: 7,
          j: {
            k: 8,
            l: 9,
          },
        },
      },
    },
  })
  const expected = {
    'a': 1,
    'b.c': 3,
    'b.d.e': 5,
    'b.d.z': 4,
    'b.d.f.h': 6,
    'b.d.f.i': 7,
    'b.d.f.j.k': 8,
    'b.d.f.j.l': 9,
  }
  expectTypeOf(result['b.c']).toEqualTypeOf<number>()
  expect(result).toEqual(expected)
})

---------------

groupBy


groupBy<T, K extends string = string>(fn: (x: T) => K): (list: T[]) => Partial<Record<K, T[]>>

It splits list according to a provided groupFn function and returns an object.

const list = [ 'a', 'b', 'aa', 'bb' ]
const groupFn = x => x.length

const result = R.groupBy(groupFn)(list)
// => { '1': ['a', 'b'], '2': ['aa', 'bb'] }

Try this R.groupBy example in Rambda REPL

All TypeScript definitions
groupBy<T, K extends string = string>(fn: (x: T) => K): (list: T[]) => Partial<Record<K, T[]>>;
R.groupBy source
export function groupByFallback(groupFn, list) {
    const result = {}
    for (let i = 0; i < list.length; i++) {
      const item = list[i]
      const key = groupFn(item)

      if (!result[key]) {
        result[key] = []
      }

      result[key].push(item)
    }

    return result
}

export function groupBy(groupFn) {
  return iterable => Object.groupBy ? Object.groupBy(iterable,groupFn) : groupByFallback(groupFn, iterable)
}
Tests
import { groupBy } from './groupBy'
import { pipe } from './pipe'

test('happy', () => {
  const groupByFn = (x: string) => String(x.length)
  const list = ['foo', 'bar']
  const result = pipe(list, groupBy(groupByFn))
  expectTypeOf(result).toEqualTypeOf<Partial<Record<string, string[]>>>()
  expect(result).toEqual({ '3': ['foo', 'bar'] })
})

test('with list', () => {
  const inventory = [
    { name: 'asparagus', type: 'vegetables', quantity: 9 },
    { name: 'bananas', type: 'fruit', quantity: 5 },
    { name: 'goat', type: 'meat', quantity: 23 },
    { name: 'cherries', type: 'fruit', quantity: 12 },
    { name: 'fish', type: 'meat', quantity: 22 },
  ]
  const result = groupBy(
    ({ quantity }: { quantity: number }) =>
      quantity < 6 ? 'restock' : 'sufficient'
  )(inventory)
  expect(result.restock).toEqual([
    { name: 'bananas', type: 'fruit', quantity: 5 },
  ])
  expect(result.sufficient![0]).toEqual(
    { name: 'asparagus', type: 'vegetables', quantity: 9 }
  )
})

---------------


head<T>(listOrString: T): T extends string ? string :
	T extends [] ? undefined:
		T extends readonly [infer F, ...infer R] ? F :
			T extends readonly [infer F] ? F :
				T extends [infer F] ? F :
					T extends [infer F, ...infer R] ? F :
						T extends unknown[] ? T[number] :
							undefined

It returns the first element of list or string input. It returns undefined if array has length of 0.

const result = [
  R.head([1, 2, 3]),
  R.head('foo')
]
// => [1, 'f']

Try this R.head example in Rambda REPL

All TypeScript definitions
head<T>(listOrString: T): T extends string ? string :
	T extends [] ? undefined:
		T extends readonly [infer F, ...infer R] ? F :
			T extends readonly [infer F] ? F :
				T extends [infer F] ? F :
					T extends [infer F, ...infer R] ? F :
						T extends unknown[] ? T[number] :
							undefined;
R.head source
export function head(listOrString) {
  if (typeof listOrString === 'string') {
    return listOrString[0] || ''
  }

  return listOrString[0]
}
Tests
import { head } from './head'

test('happy', () => {
  expectTypeOf(head('foo')).toEqualTypeOf<string>()
  expectTypeOf(head('')).toEqualTypeOf<string>()
  expectTypeOf(head([1, 2, 3])).toEqualTypeOf<number>()
  expectTypeOf(head([])).toEqualTypeOf<never>()
  expect(head(['fi', 'fo', 'fum'])).toBe('fi')
	
  expectTypeOf(head([1, 'foo', 3, 'bar'])).toEqualTypeOf<string | number>()
})

---------------

includes


includes<T>(list: readonly T[]): (target: T) => boolean

If input is string, then this method work as native String.includes.

If input is array, then R.equals is used to define if valueToFind belongs to the list.

const result = [
  R.includes('foo')('oo'),
  R.includes([{a: 1}])({a: 1})
]
// => [true, true ]

Try this R.includes example in Rambda REPL

All TypeScript definitions
includes<T>(list: readonly T[]): (target: T) => boolean;
includes(list: readonly string[] | string): (substringToFind: string) => boolean;
R.includes source
import { isArray } from './_internals/isArray.js'
import { _indexOf } from './equals.js'

export function includes(iterable) {
  return valueToFind => {
    if (typeof iterable === 'string') {
      return iterable.includes(valueToFind)
    }
    if (!iterable) {
      throw new TypeError(`Cannot read property \'indexOf\' of ${iterable}`)
    }
    if (!isArray(iterable)) {
      return false
    }

    return _indexOf(valueToFind, iterable) > -1
  }
}
Tests
import { includes } from './includes'
import { pipe } from './pipe'

test('happy', () => {
  const list = [{ a: { b: '1' } }, { a: { b: '2' } }, { a: { b: '3' } }]
  const result = pipe({ a: { b: '1' } }, includes(list))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBeTruthy()
})

test('with string as iterable', () => {
  const str = 'foo bar'
  expect(includes(str)('foo')).toBeTruthy()
  expect(includes(str)('never')).toBeFalsy()
})

test('with array as iterable', () => {
  const arr = [1, 2, 3]
  expect(includes(arr)(2)).toBeTruthy()
  expect(includes(arr)(4)).toBeFalsy()
})

test('with list of objects as iterable', () => {
  const arr = [{ a: 1 }, { b: 2 }, { c: 3 }]
  expect(includes(arr)({ c: 3 })).toBeTruthy()
})

test('with NaN', () => {
  expect(includes([Number.NaN])(Number.NaN)).toBeTruthy()
})

test('with wrong input that does not throw', () => {
  expect(includes([1])(/foo/g as never)).toBeFalsy()
})

---------------

indexBy


indexBy<T, K extends keyof T>(
  property: K
): (list: readonly T[]) => Record<string, T>

It transforms list of objects to object using specified property as the base for the returned object.

const result = R.indexBy(
	'id'
)([{id: 'xyz', title: 'A'}, {id: 'abc', title: 'B'}])
// => {abc: {id: 'abc', title: 'B'}, xyz: {id: 'xyz', title: 'A'}}

Try this R.indexBy example in Rambda REPL

All TypeScript definitions
indexBy<T, K extends keyof T>(
  property: K
): (list: readonly T[]) => Record<string, T>;
indexBy<T, K extends keyof T>(
  property: K
): (list: T[]) => Record<string, T>;
R.indexBy source
export function indexBy(property){
	return list => {
		const toReturn = {}
		for (let i = 0; i < list.length; i++){
			const item = list[ i ]
			const key = item[property]
			if(key !== undefined){
				toReturn[ key ] = item
			}
		}
	
		return toReturn
	}
}
Tests
import { indexBy } from './indexBy'
import { pipe } from './pipe'

test('happy', () => {
  const list = [{ id: 'xyz', title: 'A' }, { id: 'abc', title: 'B' }]
  const result = pipe(list, indexBy('id'))
  expectTypeOf(result.abc).toEqualTypeOf<{ id: string; title: string; }>()
  expect(result).toEqual({ abc: { id: 'abc', title: 'B' }, xyz: { id: 'xyz', title: 'A' } })
})

---------------

indexOf


indexOf<T>(valueToFind: T): (list: T[]) => number

It uses R.equals for list of objects/arrays or native indexOf for any other case.

const result = [
  R.indexOf({a:1})([{a:1}, {a:2}]),
  R.indexOf(2)([1, 2, 3]),
]
// => [0, 1]

Try this R.indexOf example in Rambda REPL

All TypeScript definitions
indexOf<T>(valueToFind: T): (list: T[]) => number;
R.indexOf source
import { _indexOf } from './equals.js'

export function indexOf(valueToFind) {
  return list => _indexOf(valueToFind, list)
}
Tests
import { indexOf } from './indexOf'

test('happy', () => {
  const list = [{ a: 1 }, { a: 2 }]
  const result = indexOf({ a: 1 })(list)
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(0)
})

test('with NaN', () => {
  expect(indexOf(Number.NaN)([Number.NaN])).toBe(0)
})

test('will throw with bad input', () => {
  expect(() => indexOf([])(true as any)).toThrow()
})

test('with numbers', () => {
  expect(indexOf(3)([1, 2, 3, 4])).toBe(2)
  expect(indexOf(10)([1, 2, 3, 4])).toBe(-1)
})

test('list of objects use R.equals', () => {
  const listOfObjects = [{ a: 1 }, { b: 2 }, { c: 3 }]
  expect(indexOf({ c: 4 } as any)(listOfObjects)).toBe(-1)
  expect(indexOf({ c: 3 } as any)(listOfObjects)).toBe(2)
})

test('list of arrays use R.equals', () => {
  const listOfLists = [[1], [2, 3], [2, 3, 4], [2, 3], [1], []]
  expect(indexOf([] as number[])(listOfLists)).toBe(5)
  expect(indexOf([1])(listOfLists)).toBe(0)
  expect(indexOf([2, 3, 4])(listOfLists)).toBe(2)
  expect(indexOf([2, 3, 5])(listOfLists)).toBe(-1)
})

---------------

init


init<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : 
	T extends [...infer U, any] ? U : T : T extends string ? string : never

It returns all but the last element of list or string input.

const result = [
  R.init([1, 2, 3]) ,
  R.init('foo')  // => 'fo'
]
// => [[1, 2], 'fo']

Try this R.init example in Rambda REPL

All TypeScript definitions
init<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : 
	T extends [...infer U, any] ? U : T : T extends string ? string : never;
R.init source
import { baseSlice } from './_internals/baseSlice.js'

export function init(input) {
  if (typeof input === 'string') {
    return input.slice(0, -1)
  }

  return input.length ? baseSlice(input, 0, -1) : []
}
Tests
import { init } from './init'
import { pipe } from './pipe'

test('happy', () => {
  expectTypeOf(init('foo')).toEqualTypeOf<string>()
  expect(init('foo')).toBe('fo')

  const result = pipe(['foo', 'bar', 1, 2, 3], init)
  expectTypeOf(result).toEqualTypeOf<(string | number)[]>()
  expect(result).toEqual(['foo', 'bar', 1, 2])
})

test('with array', () => {
  expect(init([1, 2, 3])).toEqual([1, 2])
  expect(init([1, 2])).toEqual([1])
  expect(init([1])).toEqual([])
  expect(init([])).toEqual([])
})

test('with string', () => {
  expect(init('foo')).toBe('fo')
  expect(init('f')).toBe('')
  expect(init('')).toBe('')
})

---------------

interpolate


interpolate(inputWithTags: string): (templateArguments: object) => string

It generates a new string from inputWithTags by replacing all {{x}} occurrences with values provided by templateArguments.

const inputWithTags = 'foo is {{bar}} even {{a}} more'
const templateArguments = {"bar":"BAR", a: 1}

const result = R.interpolate(inputWithTags)(templateArguments)
const expected = 'foo is BAR even 1 more'
// => `result` is equal to `expected`

Try this R.interpolate example in Rambda REPL

All TypeScript definitions
interpolate(inputWithTags: string): (templateArguments: object) => string;
R.interpolate source
const getOccurrences = input => input.match(/{{\s*.+?\s*}}/g)
const getOccurrenceProp = occurrence => occurrence.replace(/{{\s*|\s*}}/g, '')

const replace = ({ inputHolder, prop, replacer }) => {
  const regexBase = `{{${prop}}}`
  const regex = new RegExp(regexBase, 'g')
  return inputHolder.replace(regex, replacer)
}

export function interpolate(input) {
  return templateInput => {
    const occurrences = getOccurrences(input)
    if (occurrences === null) {
      return input
    }
    let inputHolder = input

    for (const occurrence of occurrences) {
      const prop = getOccurrenceProp(occurrence)
      inputHolder = replace({
        inputHolder,
        prop,
        replacer: templateInput[prop],
      })
    }

    return inputHolder
  }
}
Tests
import { interpolate } from './interpolate'

test('happy', () => {
  const templateInput = 'foo {{x}} baz'
  const templateArguments = { x: 'led zeppelin' }
  const result = interpolate(templateInput)(templateArguments)
  expectTypeOf(result).toEqualTypeOf<string>()
  expect(result).toBe('foo led zeppelin baz')
})

---------------

intersection


intersection<T>(listA: T[]): (listB: T[]) => T[]

It loops through listA and listB and returns the intersection of the two according to R.equals.

const listA = [ { id : 1 }, { id : 2 }, { id : 3 }, { id : 4 } ]
const listB = [ { id : 3 }, { id : 4 }, { id : 5 }, { id : 6 } ]

const result = R.intersection(listA)(listB)
// => [{ id : 3 }, { id : 4 }]

Try this R.intersection example in Rambda REPL

All TypeScript definitions
intersection<T>(listA: T[]): (listB: T[]) => T[];
R.intersection source
import { filter } from './filter.js'
import { includes } from './includes.js'

export function intersection(listA) {
  return listB => filter(includes(listA))(listB)
}
Tests
import { intersection } from './intersection'

test('happy', () => {
  const list1 = [1, 2, 3]
  const list2 = [1, 3, 5]
  const result = intersection(list1)(list2)
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 3])
})

test('intersection', () => {
  const list1 = [1, 2, 3, 4]
  const list2 = [3, 4, 5, 6]
  expect(intersection(list1)(list2)).toEqual([3, 4])
  expect(intersection([])([])).toEqual([])
})

test('intersection with objects', () => {
  const list1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
  const list2 = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]
  expect(intersection(list1)(list2)).toEqual([{ id: 3 }, { id: 4 }])
})

test('order is the same as in Ramda', () => {
  const list = ['a', 'b', 'c', 'd']
  expect(intersection(list)(['b', 'c'])).toEqual(['b', 'c'])
  expect(intersection(list)(['c', 'b'])).toEqual(['c', 'b'])
})

---------------

intersectionWith


intersectionWith<T1, T2>(
  pred: (a: T1, b: T2) => boolean,
  list1: T1[],
): (list2: T2[]) => T1[]

It returns a new list by applying a predicate function to all elements of list1 and list2 and keeping only these elements where predicate returns true.

const list1 = [1, 2, 3, 4, 5]
const list2 = [4, 5, 6]
const predicate = (x, y) => x >= y
const result = R.intersectionWith(predicate, list1)(list2)
// => [4, 5]

Try this R.intersectionWith example in Rambda REPL

All TypeScript definitions
intersectionWith<T1, T2>(
  pred: (a: T1, b: T2) => boolean,
  list1: T1[],
): (list2: T2[]) => T1[];
R.intersectionWith source
function _includesWith(pred, x, list) {
  let idx = 0
  const len = list.length

  while (idx < len) {
    if (pred(x, list[idx])) {
      return true
    }

    idx += 1
  }

  return false
}
function _filter(fn, list) {
  let idx = 0
  const len = list.length
  const result = []

  while (idx < len) {
    if (fn(list[idx])) {
      result[result.length] = list[idx]
    }

    idx += 1
  }

  return result
}

export function intersectionWith(pred, xs) {
  return ys => _filter(x => _includesWith(pred, x, ys), xs)
}
Tests
import { intersectionWith } from './intersectionWith'
import { pipe } from './pipe'

test('happy', () => {
  const list1 = [1, 2, 3]
  const list2 = [1, 3, 5]
  const result = pipe(list1, intersectionWith((x, y) => x === y, list2))
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 3])
})

---------------

intersperse


intersperse<T>(separator: T): (list: T[]) => T[]

It adds a separator between members of list.

const list = [ 0, 1, 2, 3 ]
const separator = 10
const result = R.intersperse(separator)(list)
// => [0, 10, 1, 10, 2, 10, 3]

Try this R.intersperse example in Rambda REPL

All TypeScript definitions
intersperse<T>(separator: T): (list: T[]) => T[];
R.intersperse source
export function intersperse(separator) {
  return list => {
    let index = -1
    const len = list.length
    const willReturn = []

    while (++index < len) {
      if (index === len - 1) {
        willReturn.push(list[index])
      } else {
        willReturn.push(list[index], separator)
      }
    }

    return willReturn
  }
}
Tests
import { intersperse } from './intersperse'

test('happy', () => {
  const result = intersperse('|')(['foo', 'bar'])
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['foo', '|', 'bar'])
})

---------------

join


join<T>(glue: string): (list: T[]) => string

It returns a string of all list instances joined with a glue.

R.join('-')([1, 2, 3])  // => '1-2-3'

Try this R.join example in Rambda REPL

All TypeScript definitions
join<T>(glue: string): (list: T[]) => string;
R.join source
export function join(glue) {
  return list => list.join(glue)
}
Tests
import { join } from './join'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([1, 2, 3], join('|'))
  expectTypeOf(result).toEqualTypeOf<string>()
  expect(result).toBe('1|2|3')
})

---------------

last


last<T>(listOrString: T): T extends string ? string :
  T extends [] ? undefined :
    T extends readonly [...infer R, infer L] ? L :
      T extends readonly [infer L] ? L :
        T extends [infer L] ? L :
          T extends [...infer R, infer L] ? L :
            T extends unknown[] ? T[number] :
              undefined

It returns the last element of input, as the input can be either a string or an array. It returns undefined if array has length of 0.

const result = [
  R.last([1, 2, 3]),
  R.last('foo'),
]
// => [3, 'o']

Try this R.last example in Rambda REPL

All TypeScript definitions
last<T>(listOrString: T): T extends string ? string :
  T extends [] ? undefined :
    T extends readonly [...infer R, infer L] ? L :
      T extends readonly [infer L] ? L :
        T extends [infer L] ? L :
          T extends [...infer R, infer L] ? L :
            T extends unknown[] ? T[number] :
              undefined;
R.last source
export function last(listOrString) {
  if (typeof listOrString === 'string') {
    return listOrString[listOrString.length - 1] || ''
  }

  return listOrString[listOrString.length - 1]
}
Tests
import { last } from './last'

test('happy', () => {
  expectTypeOf(last([1, 2, 3])).toEqualTypeOf<number>()
  expectTypeOf(last([])).toEqualTypeOf<never>()
  expectTypeOf(last('abc')).toEqualTypeOf<string>()
  expect(last([1, 2, 3])).toBe(3)
})

test('with list', () => {
  expect(last([1, 2, 3])).toBe(3)
  expect(last([])).toBeUndefined()
})

test('with string', () => {
  expect(last('abc')).toBe('c')
  expect(last('')).toBe('')
})

---------------

lastIndexOf


lastIndexOf<T>(target: T): (list: T[]) => number

It returns the last index of target in list array.

R.equals is used to determine equality between target and members of list.

If there is no such index, then -1 is returned.

const list = [1, 2, 3, 1, 2, 3]
const result = [
  R.lastIndexOf(2)(list),
  R.lastIndexOf(4)(list),
]
// => [4, -1]

Try this R.lastIndexOf example in Rambda REPL

All TypeScript definitions
lastIndexOf<T>(target: T): (list: T[]) => number;
R.lastIndexOf source
import { _lastIndexOf } from './equals.js'

export function lastIndexOf(valueToFind) {
  return list => _lastIndexOf(valueToFind, list)
}
Tests
import { lastIndexOf } from './lastIndexOf'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe([{ a: 1 }, { a: 2 }, { a: 3 }], lastIndexOf({ a: 2 }))
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(1)
})

test('with NaN', () => {
  expect(lastIndexOf(Number.NaN)([Number.NaN])).toBe(0)
})

test('without list of objects - no R.equals', () => {
  expect(lastIndexOf(3)([1, 2, 3, 4])).toBe(2)
  expect(lastIndexOf(10)([1, 2, 3, 4])).toBe(-1)
})

test('list of objects uses R.equals', () => {
  const listOfObjects = [{ a: 1 }, { b: 2 }, { c: 3 }]
  expect(lastIndexOf({ c: 4 } as any)(listOfObjects)).toBe(-1)
  expect(lastIndexOf({ c: 3 } as any)(listOfObjects)).toBe(2)
})

test('list of arrays uses R.equals', () => {
  const listOfLists = [[1], [2, 3], [2, 3, 4], [2, 3], [1], []]
  expect(lastIndexOf([] as number[])(listOfLists)).toBe(5)
  expect(lastIndexOf([1])(listOfLists)).toBe(4)
  expect(lastIndexOf([2, 3, 4])(listOfLists)).toBe(2)
  expect(lastIndexOf([2, 3, 5])(listOfLists)).toBe(-1)
})

---------------

map


map<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, U>

It returns the result of looping through iterable with fn.

💥 This function doesn't work with objects (use R.mapObject instead)

const result = R.pipe(
	[1, 2],
	R.map(x => x * 2)
)
// => [2, 4]

Try this R.map example in Rambda REPL

All TypeScript definitions
map<T extends IterableContainer, U>(
	fn: (value: T[number], index: number) => U,
): (data: T) => Mapped<T, U>;
map<T extends IterableContainer, U>(
	fn: (value: T[number]) => U,
): (data: T) => Mapped<T, U>;
R.map source
export function mapFn(
	fn, list
){
	let index = 0
	const willReturn = Array(list.length)
	while (index < list.length) {
		willReturn[index] = fn(list[index], index)
		index++
	}
	return willReturn
}

export function map(fn) {
  return list => mapFn(fn, list)
}
Tests
import { map } from './map'
import { pipe } from './pipe'

const double = (x: number) => x * 2

test('happy', () => {
  expect(map<number[], number>(double)([1, 2, 3])).toEqual([2, 4, 6])
})

test('type test', () => {
  const list = [1, 2, 3]
  const result = pipe(list, x => x, map(x => String(x)))
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['1', '2', '3'])
})

---------------

mapAsync


mapAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
): (data: T) => Promise<Mapped<T, U>>

Sequential asynchronous mapping with fn over members of list.

async function fn(x){
  await R.delay(1000)

  return x+1
}

const result = await R.mapAsync(fn)([1, 2, 3])
// `result` resolves after 3 seconds to `[2, 3, 4]`

Try this R.mapAsync example in Rambda REPL

All TypeScript definitions
mapAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
): (data: T) => Promise<Mapped<T, U>>;
mapAsync<T extends IterableContainer, U>(
  fn: (value: T[number]) => Promise<U>,
): (data: T) => Promise<Mapped<T, U>>;
R.mapAsync source
export function mapAsync(fn) {
  return async list => {
    const willReturn = []
    let i = 0
    for (const x of list) {
      willReturn.push(await fn(x, i++))
    }

    return willReturn
  }
}
Tests
import { delay } from './delay'
import { map } from './map'
import { mapAsync } from './mapAsync'
import { pipeAsync } from './pipeAsync'

const rejectDelay = (a: number) =>
  new Promise((_, reject) => {
    setTimeout(() => {
      reject(a + 20)
    }, 100)
  })

test('happy', async () => {
  const indexes: number[] = []
  const fn = async (x: number, prop: number) => {
    await delay(100)
    indexes.push(prop)
    return x + 1
  }
  const result = await mapAsync<number[], number>(fn)([1, 2, 3])
  expect(result).toEqual([2, 3, 4])
  expect(indexes).toEqual([0, 1, 2])
})

test('with R.pipeAsync', async () => {
  const fn = async (x: number) => x + 1
  const result = await pipeAsync(
    [1, 2, 3],
    map(x => x + 1),
    mapAsync(async x => {
      await delay(x)
      return x
    }),
    mapAsync(fn),
    map(x => x * 10),
  )
  expect(result).toEqual([30, 40, 50])
})

test('error', async () => {
  try {
    await mapAsync<number[], unknown>(rejectDelay)([1, 2, 3])
  } catch (err) {
    expect(err).toBe(21)
  }
})

test('happy', async () => {
  const list = ['a', 'bc', 'def']
  const result = await pipeAsync(
    list,
    mapAsync(async x => {
      await delay(100)
      return x.length % 2 ? x.length + 1 : x.length + 10
    }),
    x => x,
    map(x => x + 1),
    mapAsync(async x => {
      await delay(100)
      return x + 1
    }),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([4, 14, 6])
})

---------------

mapChain


mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number], index: number) => U,
	fn2: (value: U, index: number) => V,
): (data: T) => Mapped<T, V>

Chained 2 or 3 R.map transformations as one.

const result = R.pipe(
	[1, 2],
	R.mapChain(
		x => x * 2,
		x => [x, x > 3],
	)
)
// => [[2, false], [4, true]]

Try this R.mapChain example in Rambda REPL

All TypeScript definitions
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number], index: number) => U,
	fn2: (value: U, index: number) => V,
): (data: T) => Mapped<T, V>;
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number], index: number) => U,
	fn2: (value: U) => V,
): (data: T) => Mapped<T, V>;
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number]) => U,
	fn2: (value: U, index: number) => V,
): (data: T) => Mapped<T, V>;
mapChain<T extends IterableContainer, U, V>(
	fn1: (value: T[number]) => U,
	fn2: (value: U) => V,
): (data: T) => Mapped<T, V>;
...
...
R.mapChain source
import { mapFn } from './map.js';

export function mapChain(...fns) {
  return list => {
		let result = list.slice()
		fns.forEach((fn) => {
			result = mapFn(fn, result)
		})
		return result
	}
}
Tests
import { mapChain } from './mapChain'
import { pipe } from './pipe'

const double = (x: number) => x * 2

test('happy', () => {
  expect(mapChain<number[], number, number, number>(double, double, double)([1, 2, 3])).toEqual([8, 16, 24])
})

test('type test', () => {
  const list = [1, 2, 3]
  const result = pipe(
    list,
    mapChain(
      x => String(x),
      x => x !== 'foo',
    ),
  )
  expectTypeOf(result).toEqualTypeOf<boolean[]>()
  expect(result).toEqual([true, true, true])
})

---------------

mapKeys


mapKeys<T>(fn: (prop: string, value: T) => string): (obj: Record<string, T>) => Record<string, T>

It returns a copy of obj with keys transformed by fn.

const result = R.mapKeys(
	(key, value) => key.toUpperCase()+value
	)(
	{ a: 1, b: 2 }
)
// => { A1: 1, B2: 2 }

Try this R.mapKeys example in Rambda REPL

All TypeScript definitions
mapKeys<T>(fn: (prop: string, value: T) => string): (obj: Record<string, T>) => Record<string, T>;
R.mapKeys source
export function mapKeys(fn) {
  return obj => {
		const willReturn = {}

		Object.keys(obj).forEach(key => {
			willReturn[fn(key, obj[key])] = obj[key]
		})

		return willReturn
	}
}
Tests
import { mapKeys } from './mapKeys'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(
    { a: 1, b: 2 },
    mapKeys((prop, x) => `${prop}-${x}`),
    mapKeys(prop => `${prop}-${prop}`),
  )
  expectTypeOf(result).toEqualTypeOf<Record<string, number>>()
  expect(result).toEqual({ 'a-1-a-1': 1, 'b-2-b-2': 2 })
})

---------------

mapObject


mapObject<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Value,
): (data: T) => MappedValues<T, Value>
const fn = (val, prop) => `${prop}-${val}`
const obj = {a: 1, b: 2}

const result = R.mapObject(fn)(obj)
// => {a: 'a-1', b: 'b-2'}

Try this R.mapObject example in Rambda REPL

All TypeScript definitions
mapObject<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Value,
): (data: T) => MappedValues<T, Value>;
R.mapObject source
import { keys } from './_internals/keys.js'

export function mapObject(fn) {
  return obj => {
    let index = 0
    const objKeys = keys(obj)
    const len = objKeys.length
    const willReturn = {}

    while (index < len) {
      const key = objKeys[index]
      willReturn[key] = fn(obj[key], key, obj)
      index++
    }

    return willReturn
  }
}
Tests
import { mapObject } from './mapObject'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1 }, mapObject(a => `${a}`))
  expectTypeOf(result).toEqualTypeOf<{ a: string }>()
  expect(result).toEqual({ a: '1' })
})

---------------

mapObjectAsync


mapObjectAsync<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Promise<Value>,
): (data: T) => Promise<MappedValues<T, Value>>
All TypeScript definitions
mapObjectAsync<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Promise<Value>,
): (data: T) => Promise<MappedValues<T, Value>>;
R.mapObjectAsync source
export function mapObjectAsync(fn) {
  return async obj => {
    const willReturn = {}
    for (const prop in obj) {
      willReturn[prop] = await fn(obj[prop], prop)
    }

    return willReturn
  }
}
Tests
import { delay } from './delay'
import { mapObjectAsync } from './mapObjectAsync'
import { pipeAsync } from './pipeAsync'

test('happy', async () => {
  const indexes: string[] = []
  const result = await pipeAsync(
    { a: 1, b: 2 },
    mapObjectAsync(async (x, i) => {
      await delay(100)
      indexes.push(i)
      return x + 1
    }),
  )
  expect(indexes).toEqual(['a', 'b'])
  expect(result).toEqual({ a: 2, b: 3 })
	expectTypeOf(result).toEqualTypeOf<{ a: number; b: number }>()
})

---------------

mapParallelAsync


mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
	batchSize?: number,
): (data: T) => Promise<Mapped<T, U>>

Wrapper around Promise.all for asynchronous mapping with fn over members of list. There is optional batchSize parameter to allow parallel execution to run in batches. In this case, the whole batch must complete before the next batch starts.

All TypeScript definitions
mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[number], index: number) => Promise<U>,
	batchSize?: number,
): (data: T) => Promise<Mapped<T, U>>;
mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[number]) => Promise<U>,
	batchSize?: number,
): (data: T) => Promise<Mapped<T, U>>;
R.mapParallelAsync source
export function mapParallelAsync(fn, batchSize){
	if(!batchSize) return async list =>  Promise.all(list.map(fn))

	return async list => {
		const result = []
		for(let i = 0; i < list.length; i += batchSize){
			const batch = list.slice(i, i + batchSize)
			const batchResult = await Promise.all(batch.map((x, j) => fn(x, i + j)))
			result.push(...batchResult)
		}
		return result
	}	
}
Tests
import { delay } from './delay'
import { mapParallelAsync } from './mapParallelAsync'

test('happy', async () => {
  const fn = async (x: number, i: number) => {
    await delay(100)
    return x + i
  }
  const result = await mapParallelAsync<number[], number>(fn)([1, 2, 3])
	expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([1, 3, 5])
})

test('with batchSize', async () => {
  const fn = async (x: number, i: number) => {
    await delay(100)
    return `${x}:${i}`
  }
  const result = await mapParallelAsync<number[], string>(fn, 2)([1, 2, 3, 4, 5])
  expect(result).toEqual(['1:0', '2:1', '3:2', '4:3', '5:4'])
})

---------------

mapPropObject


mapPropObject<T extends object, K extends keyof T, Value extends unknown>(
	prop: K,
	valueMapper: (
		listItem: T[K] extends ReadonlyArray<infer ElementType> ? ElementType : never,
		list: T[K] extends ReadonlyArray<any> ? T[K] : never,
	) => Value,
): (data: T) => T[K] extends ReadonlyArray<any>
	? MergeTypes<Omit<T, K> & { [P in K]: Value[] }>
	: never

Convenience method, when one needs to maps over a object property that is a list.

const result = pipe(
	{ a: [1,2,3], b: 'foo' },
	mapPropObject('a',x => {
		return {
			a: x,
			flag: x > 2,
		}
	}),
)
// => { a: [{ a: 1, flag: false },{ a: 2, flag: false }, { a: 3, flag: true }], b: 'foo' }

Try this R.mapPropObject example in Rambda REPL

All TypeScript definitions
mapPropObject<T extends object, K extends keyof T, Value extends unknown>(
	prop: K,
	valueMapper: (
		listItem: T[K] extends ReadonlyArray<infer ElementType> ? ElementType : never,
		list: T[K] extends ReadonlyArray<any> ? T[K] : never,
	) => Value,
): (data: T) => T[K] extends ReadonlyArray<any>
	? MergeTypes<Omit<T, K> & { [P in K]: Value[] }>
	: never;
mapPropObject<T extends object, K extends keyof T, Value extends unknown>(
	prop: K,
  valueMapper: (
    listItem: T[K] extends ReadonlyArray<infer ElementType> ? ElementType : never,
  ) => Value,
): (data: T) => T[K] extends ReadonlyArray<any>
  ? MergeTypes<Omit<T, K> & { [P in K]: Value[] }>
  : never;
R.mapPropObject source
export function mapPropObject(fn, prop) {
  return obj => {
		if (!Array.isArray(obj[prop])) return obj
			
			return {
				...obj,
				[prop]: obj[prop].map(fn)
			}
		}
 	 }
Tests
import { mapPropObject } from './mapPropObject'
import { pipe } from './pipe'

const fn = (x: number) => ({ a: x, flag: x > 2 })

test('happy', () => {
  const result = (pipe as any)(
    { a: [1, 2, 3], b: 'foo' },
    (mapPropObject as any)(fn, 'a'),
  )
  expect(result).toEqual({
    a: [
      { a: 1, flag: false },
      { a: 2, flag: false },
      { a: 3, flag: true },
    ],
    b: 'foo',
  })
})

test('type test', () => {
  const result = pipe(
    { a: [1, 2, 3], b: 'foo' },
    mapPropObject('a', fn),
  )
  expectTypeOf(result.a).toEqualTypeOf<{ a: number; flag: boolean }[]>()
  expectTypeOf(result.b).toEqualTypeOf<string>()
})

---------------

match


match(regExpression: RegExp | string): (str: string) => string[]

Curried version of String.prototype.match which returns empty array, when there is no match.

const result = [
  R.match('a')('foo'),
  R.match(/([a-z]a)/g)('bananas')
]
// => [[], ['ba', 'na', 'na']]

Try this R.match example in Rambda REPL

All TypeScript definitions
match(regExpression: RegExp | string): (str: string) => string[];
R.match source
export function match(pattern) {
  return input => {
    const willReturn = input.match(pattern)

    return willReturn === null ? [] : willReturn
  }
}
Tests
import { match } from './match'

test('happy', () => {
  const str = 'foo bar'
  const result = match(/foo/)(str)
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toContain('foo')
})

test('fallback', () => {
  expect(match(/a./g)('foo')).toEqual([])
})

test('with string', () => {
  expect(match('a')('foo')).toEqual([])
})

---------------

maxBy


maxBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T

It returns the greater value between x and y according to compareFn function.

const compareFn = Math.abs

R.maxBy(compareFn, 5)(-7) // => -7

Try this R.maxBy example in Rambda REPL

All TypeScript definitions
maxBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T;
R.maxBy source
export function maxBy(compareFn, x) {
  return y => (compareFn(y) > compareFn(x) ? y : x)
}
Tests
import { maxBy } from './maxBy'
import { pipe } from './pipe'

test('happy', () => {
  const first = 1
  const second = 2
  const result = pipe(second, maxBy(x => (x % 2 === 0 ? 1 : -1), first))
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

---------------

merge


merge<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>

It creates a copy of source object with overwritten newProps properties.

All TypeScript definitions
merge<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>;
R.merge source
export function merge(target) {
  return objectWithNewProps =>
    Object.assign({}, target || {}, objectWithNewProps || {})
}
Tests
import { merge } from './merge'
import { mergeTypes } from './mergeTypes'
import { pipe } from './pipe'

test('happy', () => {
  const obj = { foo: 1, bar: 2 }
  expect(merge(obj)({ bar: 20 })).toEqual({ foo: 1, bar: 20 })
})

test('type test', () => {
  const result = pipe({ foo: 1 }, merge({ bar: 2 }), mergeTypes)
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expectTypeOf(result.bar).toEqualTypeOf<number>()
  expect(result).toEqual({ foo: 1, bar: 2 })
})

---------------

mergeDeep


mergeDeep<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>
All TypeScript definitions
mergeDeep<Source>(source: Source): <T>(newProps: T) => Merge<T, Source>;
R.mergeDeep source
import { type } from './type.js'

const isObject = (x) => type(x) === 'Object'

function mergeDeepFn(source, objectWithNewProps) {
  return [source, objectWithNewProps].reduce((prev, obj) => {
    Object.keys(obj).forEach((key) => {
      const pVal = prev[key]
      const oVal = obj[key]

      if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeepFn(pVal, oVal)
      } else {
        prev[key] = oVal
      }
    })

    return prev
  }, {})
}

export function mergeDeep(source) {
  return (objectWithNewProps) => mergeDeepFn(source, objectWithNewProps)
}
Tests
import { mergeDeep } from './mergeDeep'

test('happy', () => {
  const source = { a: 1, b: [1, 2], c: { d: 1, f: 2, e: [1, 2], h: [1, 2] } }
  const objectWithNewProps = { b: [3], c: { f: 3, s: 3, e: [3] }, q: 3 }
  expect(mergeDeep(source)(objectWithNewProps)).toEqual({
    a: 1, b: [3], c: { d: 1, f: 3, e: [3], h: [1, 2], s: 3 }, q: 3,
  })
})

test('type test', () => {
  const result = mergeDeep({ a: 1 })({ b: 2 })
  expectTypeOf(result).toEqualTypeOf<{ a: number; b: number }>()
  expect(result).toEqual({ a: 1, b: 2 })
})

---------------

mergeTypes


mergeTypes<T>(x: T): MergeTypes<T>

Helper to merge all calculated TypeScript definitions into one definition. It returns its input and it is intended to be used as last method inside R.pipe chain.

All TypeScript definitions
mergeTypes<T>(x: T): MergeTypes<T>;
R.mergeTypes source
export function mergeTypes(x) {
  return x
}

---------------

middle


middle<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : 
	T extends [any, ...infer U, any] ? U : T : T extends string ? string : never

It returns all but the first and last element of input.

const result = [
  R.middle([1, 2, 3, 4]),
  R.middle('bar')
]
// => [[2, 3], 'a']

Try this R.middle example in Rambda REPL

All TypeScript definitions
middle<T extends unknown>(input: T): T extends unknown[] ? 
	T['length'] extends 0 ? [] : T['length'] extends 1 ? [] : T['length'] extends 2 ? [] : 
	T extends [any, ...infer U, any] ? U : T : T extends string ? string : never;
R.middle source
import { init } from './init.js'
import { tail } from './tail.js'

export function middle(listOrString) {
  return tail(init(listOrString))
}
Tests
import { middle } from './middle'

test('happy', () => {
  expectTypeOf(middle('foo')).toEqualTypeOf<string>()
  expectTypeOf(middle('')).toEqualTypeOf<string>()
  expectTypeOf(middle(['foo', 'bar', 1, 2, 3])).toEqualTypeOf<(string | number)[]>()
  expect(middle('abc')).toBe('b')
})

---------------

minBy


minBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T

It returns the lesser value between x and y according to compareFn function.

const compareFn = Math.abs

R.minBy(compareFn, -5)(2) // => -5

Try this R.minBy example in Rambda REPL

All TypeScript definitions
minBy<T>(compareFn: (input: T) => Ord, x: T): (y: T) => T;
R.minBy source
export function minBy(compareFn, x) {
  return y => (compareFn(y) < compareFn(x) ? y : x)
}
Tests
import { minBy } from './minBy'

test('happy', () => {
  const result = minBy<number>(Math.abs, -5)(2)
  expectTypeOf(result).toEqualTypeOf<number>()
  expect(result).toBe(2)
})

---------------

modifyItemAtIndex


modifyItemAtIndex<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[]

It replaces index in array list with the result of replaceFn(list[i]).

const result = R.pipe(
	[1, 2, 3],
	R.modifyItemAtIndex(1, x => x + 1)
) // => [1, 3, 3]

Try this R.modifyItemAtIndex example in Rambda REPL

All TypeScript definitions
modifyItemAtIndex<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
R.modifyItemAtIndex source
import { cloneList } from './_internals/cloneList.js'

export function modifyItemAtIndex(index, replaceFn) {
  return list => {
    const actualIndex = index < 0 ? list.length + index : index
    if (index >= list.length || actualIndex < 0) {
      return list
    }

    const clone = cloneList(list)
    clone[actualIndex] = replaceFn(clone[actualIndex])

    return clone
  }
}
Tests
import { modifyItemAtIndex } from './modifyItemAtIndex'

const add10 = (x: number) => x + 10

test('happy', () => {
  const result = modifyItemAtIndex(1, add10)([0, 1, 2])
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([0, 11, 2])
})

test('with negative index', () => {
  expect(modifyItemAtIndex(-2, add10)([0, 1, 2])).toEqual([0, 11, 2])
})

test('when index is out of bounds', () => {
  expect(modifyItemAtIndex(4, add10)([0, 1, 2, 3])).toEqual([0, 1, 2, 3])
  expect(modifyItemAtIndex(-5, add10)([0, 1, 2, 3])).toEqual([0, 1, 2, 3])
})

---------------

modifyPath


modifyPath<U, T>(path: [], fn: (value: U) => T): (obj: U) => T

It changes a property of object on the base of provided path and transformer function.

const result = R.modifyPath('a.b.c', x=> x+1)({a:{b: {c:1}}})
// => {a:{b: {c:2}}}

Try this R.modifyPath example in Rambda REPL

All TypeScript definitions
modifyPath<U, T>(path: [], fn: (value: U) => T): (obj: U) => T;
modifyPath<
  K0 extends keyof U,
  U,
  T
>(path: [K0], fn: (value: U[K0]) => T): (obj: U) => DeepModify<[K0], U, T>;
modifyPath<
  K0 extends string & keyof U,
  U,
  T
>(path: `${K0}`, fn: (value: U[K0]) => T): (obj: U) => DeepModify<[K0], U, T>;
modifyPath<
  K0 extends keyof U,
  K1 extends keyof U[K0],
  U,
  T
>(path: [K0, K1], fn: (value: U[K0][K1]) => T): (obj: U) => DeepModify<[K0, K1], U, T>;
...
...
R.modifyPath source
import { createPath } from './_internals/createPath.js'
import { path as pathModule } from './path.js'

function assoc(prop, newValue) {
  return obj => Object.assign({}, obj, { [prop]: newValue })
}

function modifyPathFn(pathInput, fn, obj) {
  const path = createPath(pathInput)
  if (path.length === 1) {
    return {
      ...obj,
      [path[0]]: fn(obj[path[0]]),
    }
  }
  if (pathModule(path)(obj) === undefined) {
    return obj
  }

  const val = modifyPathFn(Array.prototype.slice.call(path, 1), fn, obj[path[0]])
  if (val === obj[path[0]]) {
    return obj
  }

  return assoc(path[0], val)(obj)
}

export function modifyPath(pathInput, fn) {
  return obj => modifyPathFn(pathInput, fn, obj)
}
Tests
import { modifyPath } from './modifyPath'
import { pipe } from './pipe'

const obj = { a: { b: { c: 1 } } }

test('happy', () => {
  const result = pipe(obj, modifyPath(['a', 'b', 'c'], (x: number) => String(x)))
  expectTypeOf(result.a.b.c).toEqualTypeOf<string>()
  expect(result).toEqual({ a: { b: { c: '1' } } })
})

---------------

modifyProp


modifyProp<T, K extends keyof T>(
  prop: K,
  fn: (x: T[K]) => T[K],
): (target: T) => T

It changes a property with the result of transformer function.

const person = {
  name : 'foo',
  age  : 20,
}
const result = R.modifyProp('age', x => x + 1)(person)
// => {name: 'foo', age: 21}

Try this R.modifyProp example in Rambda REPL

All TypeScript definitions
modifyProp<T, K extends keyof T>(
  prop: K,
  fn: (x: T[K]) => T[K],
): (target: T) => T;
R.modifyProp source
import { isArray } from './_internals/isArray.js'
import { update } from './update.js'

function modifyFn(property, fn, list) {
  if (list[property] === undefined) {
    return list
  }
  if (isArray(list)) {
    return update(property, fn(list[property]))(list)
  }

  return {
    ...list,
    [property]: fn(list[property]),
  }
}

export function modifyProp(property, fn) {
  return obj => modifyFn(property, fn, obj)
}
Tests
import { modifyProp } from './modifyProp'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 2, c: { d: 3 } }, modifyProp('a', val => val + 1))
  expectTypeOf(result).toEqualTypeOf<{ a: number; b: number; c: { d: number } }>()
  expect(result).toEqual({ a: 2, b: 2, c: { d: 3 } })
})

test('property is missing', () => {
	const person = { name: 'foo', age: 20 }
	
	expect(modifyProp<typeof person, 'age'>('foo' as 'age', (x: number) => x + 1)(person)).toEqual(person)
})

test('adjust if `array` at the given key', () => {
  expect(modifyProp<number[], 1>(1, (x: number) => x + 1)([100, 1400])).toEqual([100, 1401])
})

---------------

none


none<T>(predicate: (x: T) => boolean): (list: T[]) => boolean

It returns true, if all members of array list returns false, when applied as argument to predicate function.

const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > 6

const result = R.none(predicate)(arr)
// => true

Try this R.none example in Rambda REPL

All TypeScript definitions
none<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.none source
export function none(predicate) {
  return list => {
    for (let i = 0; i < list.length; i++) {
      if (predicate(list[i])) {
        return false
      }
    }

    return true
  }
}
Tests
import { none } from './none'
import { pipe } from './pipe'

const isEven = (n: number) => n % 2 === 0

test('happy', () => {
  const result = pipe([1, 2, 3], none(x => x > 0))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBeFalsy()
})

test('when true', () => {
  expect(none(isEven)([1, 3, 5, 7])).toBeTruthy()
})

---------------

objectIncludes


objectIncludes<T>(specification: T): (obj: Partial<T>) => boolean

It will return true if specification object fully or partially include obj object.

R.equals is used to determine equality.

const specification = { a : { b : 1 } }
const input = {
  a : { b : 1 },
  c : 2
}

const result = objectIncludes(specification)(input)
// => true

Try this R.objectIncludes example in Rambda REPL

All TypeScript definitions
objectIncludes<T>(specification: T): (obj: Partial<T>) => boolean;
R.objectIncludes source
import { equals } from './equals.js'
import { filterObject } from './filterObject.js'

export function objectIncludes(condition) {
  return obj => {
    const result = filterObject((conditionValue, conditionProp) =>
      equals(conditionValue)(obj[conditionProp]),
    )(condition)

    return Object.keys(result).length === Object.keys(condition).length
  }
}
Tests
import { objectIncludes } from './objectIncludes'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 2, c: { d: 3 } }, objectIncludes({ a: 2 }))
  expectTypeOf(result).toEqualTypeOf<boolean>()
  expect(result).toBeFalsy()
})

test('when true', () => {
  const condition = { a: 1 }
  const input = { a: 1, b: 2 }
  expect(objectIncludes(condition)(input)).toBeTruthy()
})

test('when false', () => {
  const condition = { a: 1 }
  const input = { b: 2 }
  expect(objectIncludes(condition as Record<string, number>)(input)).toBeFalsy()
})

test('with nested object', () => {
  const condition = { a: { b: 1 } }
  const input = { a: { b: 1 }, c: 2 }
  expect(objectIncludes(condition)(input)).toBeTruthy()
})

test('with wrong input', () => {
  const condition = { a: { b: 1 } }
  expect(() => objectIncludes(condition)(null as any)).toThrow()
})

---------------

objOf


objOf<T, K extends PropertyKey>(key: K): (value: T) => { [P in K]: T }

It creates an object with a single key-value pair.

const result = R.objOf('foo')('bar')
// => {foo: 'bar'}

Try this R.objOf example in Rambda REPL

All TypeScript definitions
objOf<T, K extends PropertyKey>(key: K): (value: T) => { [P in K]: T };
R.objOf source
export function objOf(key) {
  return value => ({ [key]: value })
}
Tests
import { objOf } from './objOf'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe(42, objOf('foo'))
  expectTypeOf(result.foo).toEqualTypeOf<number>()
  expect(result).toEqual({ foo: 42 })
})

---------------

omit


omit<
	S extends string,
	Keys extends PickStringToPickPath<S>,
>(propsToPick: S): <U extends Partial<Record<ElementOf<Keys>, any>>>(
	obj: ElementOf<Keys> extends keyof U ? U : never
) => ElementOf<Keys> extends keyof U ? MergeTypes<Omit<U, ElementOf<Keys>>> : never

It returns a partial copy of an obj without propsToOmit properties.

const obj = {a: 1, b: 2, c: 3}
const propsToOmit = 'a,c,d'
const propsToOmitList = ['a', 'c', 'd']

const result = [
  R.omit(propsToOmit)(obj),
  R.omit(propsToOmitList)(obj)
]
// => [{b: 2}, {b: 2}]

Try this R.omit example in Rambda REPL

All TypeScript definitions
omit<
	S extends string,
	Keys extends PickStringToPickPath<S>,
>(propsToPick: S): <U extends Partial<Record<ElementOf<Keys>, any>>>(
	obj: ElementOf<Keys> extends keyof U ? U : never
) => ElementOf<Keys> extends keyof U ? MergeTypes<Omit<U, ElementOf<Keys>>> : never;
omit<const Keys extends PropertyKey[]>(propsToPick: Keys): <
	U extends Partial<Record<ElementOf<Keys>, any>>
>(
	obj: ElementOf<Keys> extends keyof U ? U : never
) => ElementOf<Keys> extends keyof U ? MergeTypes<Omit<U, ElementOf<Keys>>> : never;
R.omit source
import { createPath } from './_internals/createPath.js'

export function _includes(x, list) {
  let index = -1
  const { length } = list

  while (++index < length) {
    if (String(list[index]) === String(x)) {
      return true
    }
  }

  return false
}

export function omit(propsToOmit) {
  return obj => {
    if (!obj) {
      return undefined
    }

    const propsToOmitValue = createPath(propsToOmit, ',')
    const willReturn = {}

    for (const key in obj) {
      if (!_includes(key, propsToOmitValue)) {
        willReturn[key] = obj[key]
      }
    }

    return willReturn
  }
}
Tests
import { omit } from './omit'
import { pipe } from './pipe'

test('happy', () => {
  const input = { a: 'foo', b: 2, c: 3 }
  const result = pipe(input, omit('a,b'))
  expectTypeOf(result).toEqualTypeOf<{ c: number }	>()
  expect(result).toEqual({ c: 3 })
})

test('with string as condition', () => {
  const obj = { a: 1, b: 2, c: 3 }
  expect(omit('a,c')(obj)).toEqual({ b: 2 })
})

test('with array as condition', () => {
  expect(omit(['a', 'c', 'd'])({ a: 'foo', b: 'bar', c: 'baz' } as any)).toEqual({ b: 'bar' })
})

---------------

partition


partition<T, S extends T>(
  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
): (data: ReadonlyArray<T>) => [Array<S>, Array<Exclude<T, S>>]

It will return array of two arrays according to predicate function. The first member holds all instances of input that pass the predicate function, while the second member - those who doesn't.

const list = [1, 2, 3]
const predicate = x => x > 2

const result = R.partition(predicate)(list)

const expected = [[3], [1, 2]]
// `result` is equal to `expected`

Try this R.partition example in Rambda REPL

All TypeScript definitions
partition<T, S extends T>(
  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
): (data: ReadonlyArray<T>) => [Array<S>, Array<Exclude<T, S>>];
partition<T>(
  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
): (data: ReadonlyArray<T>) => [Array<T>, Array<T>];
R.partition source
export function partition(predicate) {
  return list => {
		const yes = []
		const no = []
		let counter = -1
	
		while (counter++ < list.length - 1) {
			if (predicate(list[counter], counter)) {
				yes.push(list[counter])
			} else {
				no.push(list[counter])
			}
		}
	
		return [yes, no]
  }
}
Tests
import { partition } from './partition'
import { pipe } from './pipe'

test('happy', () => {
  const list = [1, 2, 3, 4]
  const result = pipe(list, partition((x: number) => x > 2))
  expectTypeOf(result).toEqualTypeOf<[number[], number[]]>()
  expect(result).toEqual([[3, 4], [1, 2]])
})

---------------

partitionObject


partitionObject<T extends unknown, S extends T>(
  predicate: (value: T, prop: string, obj: Record<string, T>) => value is S,
): (obj: Record<string, T>) => [Record<string, S>, Record<string, Exclude<T, S>>]

It returns an array containing two objects. The first object holds all properties of the input object for which the predicate returns true, while the second object holds those that do not.

const obj = {a: 1, b: 2, c: 3}
const predicate = x => x > 2

const result = R.partition(predicate)(obj)

const expected = [{c: 3},  {a: 1, b: 2}]
// `result` is equal to `expected`

Try this R.partitionObject example in Rambda REPL

All TypeScript definitions
partitionObject<T extends unknown, S extends T>(
  predicate: (value: T, prop: string, obj: Record<string, T>) => value is S,
): (obj: Record<string, T>) => [Record<string, S>, Record<string, Exclude<T, S>>];
partitionObject<T extends unknown>(
  predicate: (value: T, prop: string, obj: Record<string, T>) => boolean,
): (obj: Record<string, T>) => [Record<string, T>, Record<string, T>];
R.partitionObject source
export function partitionObject(predicate) {
	return obj => {
  const yes = {}
  const no = {}
  Object.entries(obj).forEach(([prop, value]) => {
    if (predicate(value, prop)) {
      yes[prop] = value
    } else {
      no[prop] = value
    }
  })

  return [yes, no]
}
}
Tests
import { partitionObject } from './partitionObject'
import { pipe } from './pipe'

test('happy', () => {
  const result = pipe({ a: 1, b: 2 }, partitionObject((x, prop) => x > 1 || prop === 'c'))
  expectTypeOf(result).toEqualTypeOf<[Record<string, number>, Record<string, number>]>()
  expect(result).toEqual([{ b: 2 }, { a: 1 }])
})

---------------

path


path<S, K0 extends string & keyof S>(path: `${K0}`): (obj: S) => S[K0]

If pathToSearch is 'a.b' then it will return 1 if obj is {a:{b:1}}.

It will return undefined, if such path is not found.

💥 String annotation of pathToSearch is one of the differences between Rambda and Ramda.

const obj = {a: {b: 1}}
const pathToSearch = 'a.b'
const pathToSearchList = ['a', 'b']

const result = [
  R.path(pathToSearch)(obj),
  R.path(pathToSearchList)(obj),
  R.path('a.b.c.d')(obj)
]
// => [1, 1, undefined]

Try this R.path example in Rambda REPL

All TypeScript definitions
path<S, K0 extends string & keyof S>(path: `${K0}`): (obj: S) => S[K0];
path<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(path: `${K0}.${K1}`): (obj: S) => S[K0][K1];
path<
  S,
  K0 extends keyof S,
  K1 extends keyof S[K0],
  K2 extends keyof S[K0][K1]
>(path: [K0, K1, K2]): (obj: S) => S[K0][K1][K2];
path<
  S,
  K0 extends string & keyof S,
  K1 extends string & keyof S[K0],
  K2 extends string & keyof S[K0][K1]
>(path: `${K0}.${K1}.${K2}`): (obj: S) => S[K0][K1][K2];
...
...
R.path source
import { createPath } from './_internals/createPath.js'

export function path(pathInput) {
	return (obj)  => {
		if (!obj) {
			return undefined
		}
		let willReturn = obj
		let counter = 0
	
		const pathArrValue = createPath(pathInput)
	
		while (counter < pathArrValue.length) {
			if (willReturn === null || willReturn === undefined) {
				return undefined
			}
			if (willReturn[pathArrValue[counter]] === null) {
				return undefined
			}
	
			willReturn = willReturn[pathArrValue[counter]]
			counter++
		}
	
		return willReturn
	}
}
Tests
import { path } from './path'
import { pipe } from './pipe'

test('happy', () => {
  const input = { a: { b: { c: true } } }
  const result = pipe(input, path(['a', 'b']))
	expectTypeOf(result).toEqualTypeOf<{ c: boolean }>()
  expect(result).toEqual({ c: true })
})

---------------

pathSatisfies


pathSatisfies<S, K0 extends string & keyof S>(
  predicate: (x: S[K0]) => boolean,
  path: [K0]
): (obj: S) => boolean
const result = R.pathSatisfies(
  x => x > 0,
  ['a', 'b', 'c'],
  {a: {b: {c: 1}}}
)
// => true

Try this R.pathSatisfies example in Rambda REPL

All TypeScript definitions
pathSatisfies<S, K0 extends string & keyof S>(
  predicate: (x: S[K0]) => boolean,
  path: [K0]
): (obj: S) => boolean;
pathSatisfies<S, K0 extends string & keyof S>(
  predicate: (x: S[K0]) => boolean,
  path: `${K0}`
): (obj: S) => boolean;
pathSatisfies<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  predicate: (x: S[K0][K1]) => boolean,
  path: [K0, K1]
): (obj: S) => boolean;
pathSatisfies<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  predicate: (x: S[K0][K1]) => boolean,
  path: `${K0}.${K1}`
): (obj: S) => boolean;
...
...
R.pathSatisfies source
import { path } from './path.js'

export function pathSatisfies(fn, pathInput) {
  return obj => Boolean(fn(path(pathInput)(obj)))
}
Tests
import { pathSatisfies } from './pathSatisfies'
import { pipe } from './pipe'

const isPositive = (n: number) => n > 0

test('returns true if the specified object path satisfies the given predicate', () => {
	const result = pipe({ x: { y: 1 } },pathSatisfies(isPositive, ['x', 'y']))
  expect(result).toBe(true)
	expectTypeOf(result).toEqualTypeOf<boolean>()
})

test('returns false if the specified path does not exist', () => {
	// @ts-expect-error
  expect(pipe({ x: { z: 42 } }, pathSatisfies(isPositive, ['x', 'y']))).toBe(false)
	// @ts-expect-error
  expect(pipe({ x: { z: 42 } }, pathSatisfies(isPositive, 'x.y'))).toBe(false)
})

test('returns false otherwise', () => {
  expect(pipe({ x: { y: 0 } }, pathSatisfies(isPositive, ['x', 'y']))).toBe(false)
})

---------------

permutations


permutations<T>(list: T[]): T[][]
const result = R.permutations(
	[1, 2]
)
// => [[1, 2], [2, 1]]

Try this R.permutations example in Rambda REPL

All TypeScript definitions
permutations<T>(list: T[]): T[][];
R.permutations source
import { cloneList } from './_internals/cloneList.js'

/**
 * Source:
 * https://github.com/denoland/std/blob/main/collections/permutations.ts
 */
export function permutations(inputArray) {
  const result = [];
  const array = cloneList(inputArray);
  const k = array.length;
  if (k === 0) {
    return result;
  }

  const c = new Array(k).fill(0);

  result.push([...array]);

  let i = 1;

  while (i < k) {
    if (c[i] < i) {
      if (i % 2 === 0) {
        [array[0], array[i]] = [array[i], array[0]]
      } else {
        [array[c[i]], array[i]] = [array[i], array[c[i]]]
      }

      result.push([...array]);

      c[i] += 1;
      i = 1;
    } else {
      c[i] = 0;
      i += 1;
    }
  }

  return result;
}

---------------

pick


pick<K extends PropertyKey>(propsToPick: K[]): <T extends Partial<Record<K, any>>>(input: K extends keyof T ? T : never) => MergeTypes<Pick<T, K>>

It returns a partial copy of an input containing only propsToPick properties.

input can be either an object or an array.

String annotation of propsToPick is one of the differences between Rambda and Ramda.

💥 Typescript Note: Pass explicit type annotation when used with R.pipe/R.compose for better type inference

const obj = {
  a : 1,
  b : false,
  foo: 'cherry'
}
const propsToPick = 'a,foo'
const propsToPickList = ['a', 'foo']

const result = [
  R.pick(propsToPick)(obj),
  R.pick(propsToPickList)(obj),
  R.pick('a,bar')(obj),
  R.pick('bar')(obj),
]

const expected = [
  {a:1, foo: 'cherry'},
  {a:1, foo: 'cherry'},
  {a:1},
  {},
]
// => `result` is equal to `expected`

Try this R.pick example in Rambda REPL

All TypeScript definitions
pick<K extends PropertyKey>(propsToPick: K[]): <T extends Partial<Record<K, any>>>(input: K extends keyof T ? T : never) => MergeTypes<Pick<T, K>>;
pick<S extends string, Keys extends PickStringToPickPath<S>>(propsToPick: S): <T extends Partial<Record<ElementOf<Keys>, any>>>(input: ElementOf<Keys> extends keyof T ? T : never) => ElementOf<Keys> extends keyof T ? MergeTypes<Pick<T, ElementOf<Keys>>> : never;
R.pick source
import { createPath } from './_internals/createPath.js'

export function pick(propsToPick) {
  return input => {
    if (!input === null) {
      return undefined
    }
    const keys = createPath(propsToPick, ',')
    const willReturn = {}
    let counter = 0

    while (counter < keys.length) {
      if (keys[counter] in input) {
        willReturn[keys[counter]] = input[keys[counter]]
      }
      counter++
    }

    return willReturn
  }
}
Tests
import { pick } from './pick'
import { pipe } from './pipe'

test('happy', () => {
  const input = { a: 'foo', c: 3 }
  const result = pipe(input, pick('a,c'))
  expectTypeOf(result.a).toEqualTypeOf<string>()
  expectTypeOf(result.c).toEqualTypeOf<number>()
  expect(result).toEqual({ a: 'foo', c: 3 })
})

test('props to pick is a string', () => {
  const obj = { a: 1, b: 2, c: 3 }
  expect(pick('a,c')(obj)).toEqual({ a: 1, c: 3 })
})

test('when prop is missing', () => {
  const obj = { a: 1, b: 2, c: 3 }
  expect(pick('a,d,f')(obj as any)).toEqual({ a: 1 })
})

test('props to pick is an array', () => {
  expect(pick(['a', 'c'])({ a: 'foo', b: 'bar' } as any)).toEqual({ a: 'foo' })
})

---------------

pipe


pipe<A, B>(value: A, op1: (input: A) => B): B

It performs left-to-right function composition, where first argument is the input for the chain of functions.

This is huge difference from Ramda.pipe where input is passed like R.pipe(...fns)(input). Here we have R.pipe(input, ...fns).

It has much better TypeScript support than Ramda.pipe and this is the reason why Rambda goes in this direction.

const result = R.pipe(
  [1, 2, 3],
  R.filter(x => x > 1),
  R.map(x => x*10),
)
// => [20, 30]

Try this R.pipe example in Rambda REPL

All TypeScript definitions
pipe<A, B>(value: A, op1: (input: A) => B): B;
pipe<A, B, C>(
  value: A,
  op1: (input: A) => B,
  op2: (input: B) => C,
): C;
pipe<A, B, C, D>(
  value: A,
  op1: (input: A) => B,
  op2: (input: B) => C,
  op3: (input: C) => D,
): D;
pipe<A, B, C, D, E>(
  value: A,
  op1: (input: A) => B,
  op2: (input: B) => C,
  op3: (input: C) => D,
  op4: (input: D) => E,
): E;
...
...
R.pipe source
import { reduce } from './reduce.js'

export function _arity(n, fn) {
  switch (n) {
    case 0:
      return function () {
        return fn.apply(this, arguments)
      }
    case 1:
      return function (a0) {
        return fn.apply(this, arguments)
      }
    case 2:
      return function (a0, a1) {
        return fn.apply(this, arguments)
      }
    case 3:
      return function (a0, a1, a2) {
        return fn.apply(this, arguments)
      }
    case 4:
      return function (a0, a1, a2, a3) {
        return fn.apply(this, arguments)
      }
    case 5:
      return function (a0, a1, a2, a3, a4) {
        return fn.apply(this, arguments)
      }
    case 6:
      return function (a0, a1, a2, a3, a4, a5) {
        return fn.apply(this, arguments)
      }
    case 7:
      return function (a0, a1, a2, a3, a4, a5, a6) {
        return fn.apply(this, arguments)
      }
    case 8:
      return function (a0, a1, a2, a3, a4, a5, a6, a7) {
        return fn.apply(this, arguments)
      }
    case 9:
      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) {
        return fn.apply(this, arguments)
      }
    case 10:
      return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {
        return fn.apply(this, arguments)
      }
    default:
      throw new Error(
        'First argument to _arity must be a non-negative integer no greater than ten',
      )
  }
}

function _pipe(f, g) {
  return function () {
    return g.call(this, f.apply(this, arguments))
  }
}

function pipeFn() {
  if (arguments.length === 0) {
    throw new Error('pipe requires at least one argument')
  }

  return _arity(
    arguments[0].length,
    reduce(
      _pipe,
      arguments[0],
    )(Array.prototype.slice.call(arguments, 1, Number.POSITIVE_INFINITY)),
  )
}

export function pipe(...inputs) {
  const [input, ...fnList] = inputs

  return pipeFn(...fnList)(input)
}
Tests
import { filter } from './filter'
import { map } from './map'
import { pipe } from './pipe'
import { split } from './split'

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    filter(x => x > 1),
    map(x => x * 10),
    map(x => x + 1),
  )
  expect(result).toEqual([21, 31])
})

test('split test', () => {
  const tableData = `id,title,year
1,The First,2001
2,The Second,2020
3,The Third,2018`

  const result = pipe(tableData, split('\n'), map(split(',')))
  expectTypeOf(result).toEqualTypeOf<string[][]>()
  expect(result).toEqual([
    ['id', 'title', 'year'],
    ['1', 'The First', '2001'],
    ['2', 'The Second', '2020'],
    ['3', 'The Third', '2018'],
  ])
})

test('R.pipe type test', () => {
  const obj = { a: 'foo', b: 'bar' }
  const result = pipe(
    obj,
    x => ({ a: x.a.length + x.b.length }),
    x => ({ ...x, b: x.a + 'foo' }),
    x => ({ ...x, c: x.b + 'bar' }),
  )
  expectTypeOf(result.a).toEqualTypeOf<number>()
  expectTypeOf(result.b).toEqualTypeOf<string>()
  expectTypeOf(result.c).toEqualTypeOf<string>()
  expect(result.a > 0).toBeTruthy()
})

---------------

pipeAsync


pipeAsync<A, B>(input: A, fn0: (x: Awaited<A>) => B) : B

It accepts input as first argument and series of functions as next arguments. It is same as R.pipe but with support for asynchronous functions.

const result = await R.pipeAsync(
  100,
  async x => {
    await R.delay(100)
    return x + 2
  },
  x => x +2,
  async x => {
    const delayed = await R.delay(100)
    return delayed + x
  }
)
// `result` resolves to `RAMBDAX_DELAY104`

Try this R.pipeAsync example in Rambda REPL

All TypeScript definitions
pipeAsync<A, B>(input: A, fn0: (x: Awaited<A>) => B) : B;
pipeAsync<A, B, C>(input: A, fn0: (x: Awaited<A>) => B, fn1: (x: Awaited<B>) => C) : C;
pipeAsync<A, B, C, D>(input: A, fn0: (x: Awaited<A>) => B, fn1: (x: Awaited<B>) => C, fn2: (x: Awaited<C>) => D) : D;
pipeAsync<A, B, C, D, E>(input: A, fn0: (x: Awaited<A>) => B, fn1: (x: Awaited<B>) => C, fn2: (x: Awaited<C>) => D, fn3: (x: Awaited<D>) => E) : E;
...
...
R.pipeAsync source
import { type } from './type.js'

export async function pipeAsync(input, ...fnList) {
  let willReturn = input
  for (const fn of fnList) {
    const initialResult = fn(willReturn)
    willReturn =
      type(initialResult) === 'Promise' ? await initialResult : initialResult
  }
  return willReturn
}
Tests
import { delay } from './delay'
import { pipeAsync } from './pipeAsync'

test('happy', async () => {
  const result = await pipeAsync(
    4,
    async x => {
      await delay(100)
      return x + 1
    },
    x => Promise.resolve([x]),
  )
  expectTypeOf(result).toEqualTypeOf<number[]>()
  expect(result).toEqual([5])
})

---------------

pluck


pluck<T, K extends keyof T>(property: K): (list: T[]) => T[K][]

It returns list of the values of property taken from the all objects inside list. Basically, this is R.map(R.prop(property)).

💥 Typescript Note: Pass explicit type annotation when used with R.pipe/R.compose for better type inference

const list = [{a: 1}, {a: 2}, {b: 3}]
const property = 'a'

const result = R.pluck(property)(list)
// => [1, 2]

Try this R.pluck example in Rambda REPL

All TypeScript definitions
pluck<T, K extends keyof T>(property: K): (list: T[]) => T[K][];
pluck<K extends PropertyKey>(prop: K): {
  <U extends O[keyof O], UK extends keyof U, O extends Record<string, any>>(obj: K extends UK ? O : never): { [OK in keyof O]: O[OK][K] };
  <U extends readonly unknown[] | Record<K, any>>(list: readonly U[]): U extends readonly (infer T)[] ? T[] : U extends Record<K, infer T> ? T[] : never;
};
...
...
R.pluck source
export function pluck(property) {
  return list => {
    const willReturn = []

    list.forEach(x => {
      if (x[property] !== undefined) {
        willReturn.push(x[property])
      }
    })

    return willReturn
  }
}
Tests
import { pluck } from './pluck'
import { pipe } from './pipe'

test('happy', () => {
  const input = [{ a: 1, b: 'foo' }, { a: 2, b: 'bar' }]
  const result = pipe(input, pluck('b'))
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['foo', 'bar'])
})

test('with undefined', () => {
  expect(pluck(undefined as any)([{ a: 1 }, { a: 2 }, { b: 1 }])).toEqual([])
})

---------------

prepend


prepend<T>(xToPrepend: T): (iterable: T[]) => T[]

It adds element x at the beginning of list.

const result = R.prepend('foo')(['bar', 'baz'])
// => ['foo', 'bar', 'baz']

Try this R.prepend example in Rambda REPL

All TypeScript definitions
prepend<T>(xToPrepend: T): (iterable: T[]) => T[];
R.prepend source
export function prepend(x) {
  return list => [x].concat(list)
}
Tests
import { prepend } from './prepend'

test('happy', () => {
  const result = prepend('yes')(['foo', 'bar', 'baz'])
  expectTypeOf(result).toEqualTypeOf<string[]>()
  expect(result).toEqual(['yes', 'foo', 'bar', 'baz'])
})

test('with empty list', () => {
  expect(prepend('foo')([])).toEqual(['foo'])
})

[![---------------](https://raw.githubusercontent.com/selfref

Truncated — view the full README on GitHub.

fp
functional
functional-programming
functions
lodash
ramda
typescript
utility
utils

Project health

Overall

3.4/10

Maintained

4/10

Code review

0/10
OpenSSF Scorecard

Latest release

11.3.0 · Sep 2, 2026

Releases this year

7

Significant stargazers

Matt Benson

18 followers · starred Feb 2023

Jamon Holmgren

1,014 followers · starred Feb 2019

MS_Y

312 followers · starred Nov 2021

Sebastian

265 followers · starred Mar 2020