rambda
Lightweight and faster alternative to Ramda with included TS definitions
Last updated 8 months ago by self_refactor .
MIT · Repository · Bugs · Original npm · Tarball · package.json
$ cnpm install rambda 
SYNC missed versions from official npm registry.

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

:exclamation: 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)

:exclamation: 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

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

test('happy', () => {
	const result = addProp('a', 1)({ b: 2 })
	const expected = { a: 1, b: 2 }

	expect(result).toEqual(expected)
})
<summary>TypeScript test</summary>
import { addProp, pipe } from 'rambda'

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

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

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

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

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

test('R.addPropToObjects', () => {
		let result = pipe(
			[
				{a: 1, b: 2},
				{a: 3, b: 4},
			],
			addPropToObjects(
				'c',
				(x) => String(x.a + x.b),
			)
		)
		expect(result).toEqual([
			{ a: 1, b: 2, c: '3' },
			{ a: 3, b: 4, c: '7' },
		])
})
<summary>TypeScript test</summary>
import { addPropToObjects, pipe } from 'rambda'

it('R.addPropToObjects', () => {
		let result = pipe(
			[
				{a: 1, b: 2},
				{a: 3, b: 4},
			],
			addPropToObjects(
				'c',
				(x) => String(x.a + x.b),
			)
		)
		result // $ExpectType { a: number; b: number; c: string; }[]
})

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

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

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

    return true
  }
}
<summary>Tests</summary>
import { all } from './all.js'

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

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

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

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

  expect(all(fn)(list)).toBeFalsy()
})
<summary>TypeScript test</summary>
import * as R from 'rambda'

describe('all', () => {
  it('happy', () => {
    const result = R.pipe(
      [1, 2, 3],
      R.all(x => {
        x // $ExpectType number
        return x > 0
      }),
    )
    result // $ExpectType boolean
  })
})

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

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

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

    return true
  }
}
<summary>Tests</summary>
import { allPass } from './allPass.js'
import { filter } from './filter.js'
import { pipe } from './pipe.js'

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

test('when returns false', () => {
  const result = pipe(list, filter(allPass([x => x.includes(12), x => x.includes(31)])))
  expect(result).toEqual([])
})
<summary>TypeScript test</summary>
import * as R from 'rambda'

describe('allPass', () => {
  it('happy', () => {
    const list = [
      [1, 2, 3, 4],
      [3, 4, 5],
    ]
    const result = R.pipe(list, R.map(R.allPass([
			(x) => x.length > 2,
			(x) => x.includes(3)
		])))
    result // $ExpectType boolean[]
  })
})

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

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

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

    return false
  }
}
<summary>Tests</summary>
import { any } from './any.js'

const list = [1, 2, 3]

test('happy', () => {
  expect(any(x => x > 2)(list)).toBeTruthy()
})
<summary>TypeScript test</summary>
import { any, pipe } from 'rambda'

it('R.any', () => {
  const result = pipe(
    [1, 2, 3],
    any(x => {
      x // $ExpectType number
      return x > 2
    }),
  )
  result // $ExpectType boolean
})

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

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

<summary>All TypeScript definitions</summary>
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;
...
...
<summary>R.anyPass source</summary>
export function anyPass(predicates) {
  return input => {
    let counter = 0
    while (counter < predicates.length) {
      if (predicates[counter](input)) {
        return true
      }
      counter++
    }

    return false
  }
}
<summary>Tests</summary>
import { anyPass } from './anyPass.js'

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

test('happy', () => {
  const rules = [x => typeof x === 'string', x => 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 => val.a === 1, val => val.a === 2]

  expect(anyPass(conditionArr)(obj)).toBeTruthy()
})

test('when returns false', () => {
  const conditionArr = [val => val.a === 2, val => val.b === 3]

  expect(anyPass(conditionArr)(obj)).toBeFalsy()
})

test('with empty predicates list', () => {
  expect(anyPass([])(3)).toBeFalsy()
})
<summary>TypeScript test</summary>
import { anyPass, filter } from 'rambda'

describe('anyPass', () => {
  it('issue #604', () => {
    const plusEq = (w: number, x: number, y: number, z: number) => w + x === y + z
    const result = anyPass([plusEq])(3, 3, 3, 3)

    result // $ExpectType boolean
  })
  it('issue #642', () => {
    const isGreater = (num: number) => num > 5
    const pred = anyPass([isGreater])
    const xs = [0, 1, 2, 3]

    const filtered1 = filter(pred)(xs)
    filtered1 // $ExpectType number[]
    const filtered2 = xs.filter(pred)
    filtered2 // $ExpectType number[]
  })
  it('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)) {
      aValue // $ExpectType 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

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

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

    return clone
  }
}
<summary>Tests</summary>
import { append } from './append.js'

test('happy', () => {
  expect(append('tests')(['write', 'more'])).toEqual(['write', 'more', 'tests'])
})

test('append to empty array', () => {
  expect(append('tests')([])).toEqual(['tests'])
})
<summary>TypeScript test</summary>
import { append, pipe, prepend } from 'rambda'

const listOfNumbers = [1, 2, 3]

describe('R.append/R.prepend', () => {
  it('happy', () => {
    const result = pipe(listOfNumbers, append(4), prepend(0))
    result // $ExpectType number[]
  })
  it('with object', () => {
    const result = pipe([{ a: 1 }], append({ a: 10 }), prepend({ a: 20 }))
    result // $ExpectType { a: number; }[]
  })
})

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

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

<summary>All TypeScript definitions</summary>
ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
<summary>R.ascend source</summary>
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)
}
}
<summary>Tests</summary>
import { ascend } from './ascend.js'
import { descend } from './descend.js'
import { sort } from './sort.js'

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

test('descend', () => {
  const result = sort(
    descend(x => x.a))(
    [{a:1}, {a:3}, {a:2}],
  )
  expect(result).toEqual([{a:3}, {a:2}, {a:1}])
})
<summary>TypeScript test</summary>
import { pipe, ascend, sort } from 'rambda'

it('R.ascend', () => {
	const result = pipe(
		[{a:1}, {a:2}],
		sort(ascend(x => x.a))
	)
	result // $ExpectType { a: number; }[]
})

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

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.

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

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

test('throw', () => {
	expect(() => {
		pipe(
			[1, 2, 3],
			assertType((x) => x.length === 4),
		)
	}).toThrow('type assertion failed in R.assertType')
})
<summary>TypeScript test</summary>
import { pipe, assertType } from 'rambda'

type Book = {
	title: string
	year: number
}

type BookToRead = Book & {
	bookmarkFlag: boolean
}

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

it('R.assertType', () => {
	const result = pipe(
		{ title: 'Book1', year: 2020, bookmarkFlag: true },
		assertType(isBookToRead),
	)
	result // $ExpectType BookToRead
})

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

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

<summary>All TypeScript definitions</summary>
checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean;
<summary>R.checkObjectWithSpec source</summary>
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
  }
}
<summary>Tests</summary>
import { checkObjectWithSpec } from './checkObjectWithSpec.js'
import { equals } from './equals.js'

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 => input => {
    counter++

    return input === expected
  }
  const predicate = checkObjectWithSpec({
    a: equalsFn('foo'),
    b: equalsFn('baz'),
  })
  expect(
    predicate({
      a: 'notfoo',
      b: 'notbar',
    }),
  ).toBeFalsy()
  expect(counter).toBe(1)
})
<summary>TypeScript test</summary>
import { checkObjectWithSpec, equals } from 'rambda'

describe('R.checkObjectWithSpec', () => {
  it('happy', () => {
    const input = {
      a: 'foo',
      b: 'bar',
      x: 11,
      y: 19,
    }
    const conditions = {
      a: equals('foo'),
      b: equals('bar'),
    }
    const result = checkObjectWithSpec(conditions)(input)
    result // $ExpectType boolean
  })
})

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

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

<summary>All TypeScript definitions</summary>
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>
};
<summary>R.compact source</summary>
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)
}
<summary>Tests</summary>
import { compact } from './compact.js'
import { pipe } from './pipe.js'

test('happy', () => {
  const result = 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: compact(x.a),
			b: compact(x.b),
			c: compact(x.c)
		})
	)
	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 })
})
<summary>TypeScript test</summary>
import { compact, pipe } from 'rambda'

it('R.compact', () => {
		let 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: compact(x.a),
				b: compact(x.b),
				c: compact(x.c)
			})
		)

		result.a // $ExpectType string[]
		result.b // $ExpectType number[]
		result.c // $ExpectType { a: number; b: number; c: number; f: boolean; }
})

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

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

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

test('happy', () => {
  const fn = complement(x => x.length === 0)

  expect(fn([1, 2, 3])).toBeTruthy()
})

test('with multiple parameters', () => {
  const between = (a, b, c) => a < b && b < c
  const f = complement(between)
  expect(f(4, 5, 11)).toBeFalsy()
  expect(f(12, 2, 6)).toBeTruthy()
})
<summary>TypeScript test</summary>
import { complement } from 'rambda'

describe('R.complement', () => {
  it('happy', () => {
    const fn = complement((x: number) => x > 10)
    const result = fn(1)
    result // $ExpectType boolean
  })
})

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

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

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

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

it('R.concat', () => {
  const result = pipe(list1, concat(list2))
  result // $ExpectType number[]
  const resultString = pipe('foo', concat('list2'))
  resultString // $ExpectType string
})

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

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.

<summary>All TypeScript definitions</summary>
convertToType<T>(x: unknown) : T;
<summary>R.convertToType source</summary>
export function convertToType(x) {
  return x
}
<summary>TypeScript test</summary>
import { convertToType, pipe } from 'rambda'

const list = [1, 2, 3]

it('R.convertToType', () => {
  const result = pipe(list, 
		convertToType<string[]>,
		x => {
			x // $ExpectType string[]
			return x 
		}
	)
  result // $ExpectType string[]
})

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

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

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

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

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

const predicate = x => x.a !== undefined

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

test('happy', () => {
  const list = [1, 2, { a: 1 }, 3, { a: 1 }]

  expect(count(predicate)(list)).toBe(2)
})
<summary>TypeScript test</summary>
import { count, pipe } from 'rambda'

const list = [1, 2, 3]
const predicate = (x: number) => x > 1

it('R.count', () => {
  const result = pipe(list, count(predicate))
  result // $ExpectType number
})

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

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

<summary>All TypeScript definitions</summary>
countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number };
<summary>R.countBy source</summary>
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
  }
}
<summary>Tests</summary>
import { countBy } from './countBy.js'

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

test('happy', () => {
  const result = countBy(x => x.toLowerCase())(list)
  expect(result).toEqual({
    a: 2,
    b: 2,
    c: 2,
  })
})
<summary>TypeScript test</summary>
import { countBy, pipe } from 'rambda'

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

it('R.countBy', () => {
  const result = pipe(
    list,
    countBy(x => x.toLowerCase()),
  )
  result.a // $ExpectType number
  result.foo // $ExpectType number
  result // $ExpectType { [index: string]: number; }
})

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

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

<summary>All TypeScript definitions</summary>
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 };
<summary>R.createObjectFromKeys source</summary>
export function createObjectFromKeys(fn) {
	return keys => {
		const result = {}
		keys.forEach((key, index) => {
			result[key] = fn(key, index)
		})

		return result
	}
}
<summary>Tests</summary>
import { createObjectFromKeys } from './createObjectFromKeys.js'

test('happy', () => {
	const result = createObjectFromKeys((key, index) => 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).

:boom: 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

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

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

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')
})
<summary>TypeScript test</summary>
import { defaultTo, pipe } from 'rambda'

describe('R.defaultTo', () => {
  it('happy', () => {
    const result = pipe('bar' as unknown, defaultTo('foo'))

    result // $ExpectType string
  })
})

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

delay


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

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

<summary>All TypeScript definitions</summary>
delay(ms: number): Promise<'RAMBDA_DELAY'>;
<summary>R.delay source</summary>
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

<summary>All TypeScript definitions</summary>
descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
<summary>R.descend source</summary>
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

<summary>All TypeScript definitions</summary>
difference<T>(x: T[]): (y: T[]) => T[];
<summary>R.difference source</summary>
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),
	])
}
<summary>Tests</summary>
import { difference } from './difference.js'

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 }]
  expect(difference(list1)(list2)).toEqual([
    { id: 1 },
    { id: 2 },
    { id: 5 },
    { id: 6 },
  ])
})
<summary>TypeScript test</summary>
import { difference } from 'rambda'

describe('R.difference', () => {
  it('happy', () => {
    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)

    result // $ExpectType { id: number; }[]
  })
})

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

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

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

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])
})
<summary>TypeScript test</summary>
import { drop, pipe } from 'rambda'

it('R.drop', () => {
  const result = pipe([1, 2, 3, 4], drop(2))
  result // $ExpectType number[]
})

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

dropLast


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

It returns howMany items dropped from the end of list.

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

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

<summary>All TypeScript definitions</summary>
dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropLastWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
<summary>R.dropLastWhile source</summary>
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()
  }
}
<summary>Tests</summary>
import { dropLastWhile } from './dropLastWhile.js'

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

test('with list', () => {
  const result = dropLastWhile(x => 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

<summary>All TypeScript definitions</summary>
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

<summary>All TypeScript definitions</summary>
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

<summary>All TypeScript definitions</summary>
dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
<summary>R.dropWhile source</summary>
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
  }
}
<summary>Tests</summary>
import { dropWhile } from './dropWhile.js'

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

test('happy', () => {
  const predicate = (x, i) => {
    expect(typeof i).toBe('number')
    return x < 3
  }
  const result = dropWhile(predicate)(list)
  expect(result).toEqual([3, 4])
})

test('always false', () => {
  const predicate = () => 0
  const result = dropWhile(predicate)(list)
  expect(result).toEqual(list)
})
<summary>TypeScript test</summary>
import { dropWhile, pipe } from 'rambda'

const list = [1, 2, 3]

describe('R.dropWhile', () => {
  it('happy', () => {
    const result = pipe(
      list,
      dropWhile(x => x > 1),
    )

    result // $ExpectType number[]
  })
  it('with index', () => {
    const result = pipe(
      list,
      dropWhile((x, i) => {
        i // $ExpectType number
        return x + i > 2
      }),
    )

    result // $ExpectType number[]
  })
})

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

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

<summary>All TypeScript definitions</summary>
duplicateBy<T, U>(fn: (x: T) => U): (list: T[]) => T[];
<summary>R.duplicateBy source</summary>
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)))
  }
}
<summary>Tests</summary>
import { duplicateBy } from './duplicateBy.js'

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

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

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

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

test('has R.equals semantics', () => {
  expect(eqBy(Math.abs, Number.NaN)(Number.NaN)).toBe(true)
  expect(eqBy(Math.abs, [42])([42])).toBe(true)
  expect(eqBy(x => x, { a: 1 })({ a: 1 })).toBe(true)
  expect(eqBy(x => 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

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

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

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

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', obj1)(obj2)
  expect(result).toBeTruthy()
})
<summary>TypeScript test</summary>
import { eqProps, pipe } from 'rambda'

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

it('R.eqProps', () => {
  const result = pipe(obj1, eqProps('a', obj2))

  result // $ExpectType boolean
})

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

equals


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

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

:boom: 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

<summary>All TypeScript definitions</summary>
equals<T>(x: T, y: T): boolean;
equals<T>(x: T): (y: T) => boolean;
<summary>R.equals source</summary>
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)
}
<summary>Tests</summary>
import { equalsFn } from './equals.js'

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

  const expectTrue = equalsFn(foo, foo)
  const expectFalseFirst = equalsFn(foo, bar)
  const expectFalseSecond = equalsFn(foo, baz)

  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(equalsFn(list1, list2)).toBeTruthy()
  expect(equalsFn(list1, list3)).toBeFalsy()
})

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

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

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

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

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

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

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

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

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

  expect(
    equalsFn(
      {
        a: 2,
        b: 3,
      },
      {
        a: 2,
        b: 3,
      },
    ),
  ).toBeTruthy()

  expect(
    equalsFn(
      {
        a: 2,
        b: 3,
      },
      {
        a: 3,
        b: 3,
      },
    ),
  ).toBeFalsy()

  expect(
    equalsFn(
      {
        a: 2,
        b: 3,
        c: 1,
      },
      {
        a: 2,
        b: 3,
      },
    ),
  ).toBeFalsy()
})

test('works with boolean tuple', () => {
  expect(equalsFn([true, false], [true, false])).toBeTruthy()
  expect(equalsFn([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(equalsFn(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(equalsFn(x, y)).toBeFalsy()
})

test('works with undefined as second argument', () => {
  expect(equalsFn(1, undefined)).toBeFalsy()

  expect(equalsFn(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(equalsFn(toCompareSame, testSet)).toBeTruthy()
  expect(equalsFn(toCompareDifferent, testSet)).toBeFalsy()
})

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

test('various examples', () => {
  expect(equalsFn([1, 2, 3], [1, 2, 3])).toBeTruthy()
  expect(equalsFn([1, 2, 3], [1, 2])).toBeFalsy()
  expect(equalsFn({}, {})).toBeTruthy()
})
<summary>TypeScript test</summary>
import { equals } from 'rambda'

describe('R.equals', () => {
  it('happy', () => {
    const result = equals(4, 1)
    result // $ExpectType boolean
  })
  it('with object', () => {
    const foo = { a: 1 }
    const bar = { a: 2 }
    const result = equals(foo, bar)
    result // $ExpectType boolean
  })
  it('curried', () => {
    const result = equals(4)(1)

    result // $ExpectType boolean
  })
})

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

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

<summary>All TypeScript definitions</summary>
evolve<T>(rules: {
	[K in keyof T]?: (x: T[K]) => T[K]
}): (obj: T) => T;
<summary>R.evolve source</summary>
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)
}
<summary>Tests</summary>
import { evolve } from './evolve.js'

test('happy', () => {
  const rules = {
    foo: x => x + 1,
  }
  const input = {
    a: 1,
    foo: 2,
		nested: { bar: { z: 3 } },
  }
  const result = evolve(rules)(input)
  expect(result).toEqual({
    a: 1,
    foo: 3,
		nested: { bar: { z: 3 } },
  })
})
<summary>TypeScript test</summary>
import {  evolve, pipe } from 'rambda'

it('R.evolve', () => {
  const input = {
		baz: 1,
    foo: 2,
    nested: {
      a: 1,
      bar: 3,
    },
  }
  const result = pipe(input, 
		evolve({
			foo: x => x + 1,
		})
	)
  result.foo // $ExpectType number
  result.baz // $ExpectType number
  result.nested.a // $ExpectType number
})

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

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

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

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

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()
})
<summary>TypeScript test</summary>
import { excludes, pipe } from 'rambda'

describe('R.excludes', () => {
  it('happy', () => {
    const list = [{ a: { b: '1' } }, { a: { b: '2' } }, { a: { b: '3' } }]
    const result = pipe({ a: { b: '1' } }, excludes(list))
    result // $ExpectType boolean
  })
  it('with string', () => {
    const result = pipe('foo', excludes('bar'))
    result // $ExpectType boolean
  })
})

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

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

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

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

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

test('happy', () => {
  const fn = propEq(2, 'a')
  expect(exists(fn)(list)).toBe(true)
})

test('nothing is found', () => {
  const fn = propEq(4, 'a')
  expect(exists(fn)(list)).toBe(false)
})
<summary>TypeScript test</summary>
import { exists, pipe } from 'rambda'

const list = [1, 2, 3]

describe('R.exists', () => {
  it('happy', () => {
    const predicate = (x: number) => x > 2
    const result = pipe(list, exists(predicate))
    result // $ExpectType boolean
  })
})

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

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

<summary>All TypeScript definitions</summary>
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[];
...
...
<summary>R.filter source</summary>
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
  }
}
<summary>Tests</summary>
import { filter } from './filter.js'

test('happy', () => {
  const isEven = n => 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])
})
<summary>TypeScript test</summary>
import { filter, includes, pipe, reject, sort, split, uniq } from 'rambda'

const list = [1, 2, 3]

describe('R.filter with array', () => {
  it('within pipe', () => {
    const result = pipe(
      list,
      filter(x => {
        x // $ExpectType number
        return x > 1
      }),
    )
    result // $ExpectType number[]
  })

  it('with index', () => {
    const result = pipe(
      list,
      filter((x: number, i: number) => {
        x // $ExpectType number
        i // $ExpectType number
        return x > 1
      }),
    )
    result // $ExpectType number[]
  })

  it('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 => {
        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, b) => (a === b ? 0 : a > b ? 1 : -1)),
      filter(char => char.trim().length > 0),
      reject(includes(SENTENCE_END_CHARS)),
    )

    result // $ExpectType string[]
  })
  it('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))
    result // $ExpectType Bar[]
  })

  it('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))
    result // $ExpectType Bar[]
  })

  it('filtering NonNullable - list of objects', () => {
    const testList = [{ a: 1 }, { a: 2 }, false, { a: 3 }]
    const result = pipe(testList, filter(Boolean))
    result // $ExpectType { a: number; }[]
  })

  it('filtering NonNullable - readonly', () => {
    const testList = [1, 2, true, false, null, undefined, 3] as const
    const result = pipe(testList, filter(Boolean))
    result.includes(1)
    // @ts-expect-error
    result.includes(true)
    // @ts-expect-error
    result.includes(false)
    // @ts-expect-error
    result.includes(4)
    // @ts-expect-error
    result.includes(undefined)
    // @ts-expect-error
    result.includes(null)
  })
})

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

filterAsync


filterAsync<T>(
	predicate: (value: T) => Promise<boolean>,
): (list: T[]) => Promise<T[]>
<summary>All TypeScript definitions</summary>
filterAsync<T>(
	predicate: (value: T) => Promise<boolean>,
): (list: T[]) => Promise<T[]>;
<summary>R.filterAsync source</summary>
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
  }
}
<summary>Tests</summary>
import { filterAsync } from './filterAsync.js'

test('happy', async () => {
  const isEven = async n => n % 2 === 0

  expect(await filterAsync(isEven)([1, 2, 3, 4])).toEqual([2, 4])
})
<summary>TypeScript test</summary>
import { filterAsync, pipeAsync } from 'rambda'

const list = [1, 2, 3]

describe('R.filter with array', () => {
  it('within pipe', async () => {
    const result = await pipeAsync(
      list,
      filterAsync(async x => {
        x // $ExpectType number
        return x > 1
      }),
    )
    result // $ExpectType number[]
  })
})

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

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.

:boom: 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

<summary>All TypeScript definitions</summary>
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>>;
<summary>R.filterMap source</summary>
import {mapFn} from './map.js'

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

const double = x => x > 1 ? x * 2 : null

it('happy', () => {
  expect(filterMap(double)([1, 2, 3])).toEqual([4, 6])
})
<summary>TypeScript test</summary>
import { filterMap, pipe } from 'rambda'

const list = [1, 2, 3]

it('R.filterMap - within pipe', () => {
  const result = pipe(
    list,
    x => x,
    filterMap(x => {
      x // $ExpectType number
      return Math.random() > 0.5 ? String(x) : null
    }),
    filterMap(x => {
      x // $ExpectType string
      return Math.random() > 0.5 ? Number(x) : ''
    }),
  )
  result // $ExpectType 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

<summary>All TypeScript definitions</summary>
filterObject<T extends object>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => boolean,
): <U extends T>(data: T) => U;
<summary>R.filterObject source</summary>
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
  }
}
<summary>Tests</summary>
import { pipe } from './pipe.js'
import { filterObject } from './filterObject.js'

test('happy', () => {
	let testInput = { a: 1, b: 2, c: 3 }
  const result = pipe(
		testInput,
		filterObject((x, prop, obj) => {
			expect(prop).toBeOneOf(['a', 'b', 'c'])
			expect(obj).toBe(testInput)
			return x > 1
		})
	)
	expect(result).toEqual({ b: 2, c: 3 })
})
<summary>TypeScript test</summary>
import { filterObject, pipe } from 'rambda'

describe('R.filterObject', () => {
  it('require explicit type', () => {
    const result = pipe(
      { a: 1, b: 2 },
      filterObject<{ b: number }>(a => {
        a // $ExpectType number
        return a > 1
      }),
    )
    result.b // $ExpectType number
  })
})

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

find


find<T>(predicate: (x: T) => boolean): (list: T[]) => T | 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

<summary>All TypeScript definitions</summary>
find<T>(predicate: (x: T) => boolean): (list: T[]) => T | undefined;
<summary>R.find source</summary>
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++
    }
  }
}
<summary>Tests</summary>
import { find } from './find.js'
import { propEq } from './propEq.js'

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

test('happy', () => {
  const fn = propEq(2, 'a')
  expect(find(fn)(list)).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()
})
<summary>TypeScript test</summary>
import { find, pipe } from 'rambda'

const list = [1, 2, 3]

describe('R.find', () => {
  it('happy', () => {
    const predicate = (x: number) => x > 2
    const result = pipe(list, find(predicate))
    result // $ExpectType number | undefined
  })
})

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

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

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

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

    return -1
  }
}
<summary>Tests</summary>
import { findIndex } from './findIndex.js'
import { propEq } from './propEq.js'

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)
})
<summary>TypeScript test</summary>
import { findIndex, pipe } from 'rambda'

const list = [1, 2, 3]

it('R.findIndex', () => {
  const result = pipe(
    list,
    findIndex(x => x > 2),
  )
  result // $ExpectType number
})

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

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

<summary>All TypeScript definitions</summary>
findLast<T>(fn: (x: T) => boolean): (list: T[]) => T | undefined;
<summary>R.findLast source</summary>
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

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

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

    return -1
  }
}
<summary>Tests</summary>
import { findLastIndex } from './findLastIndex.js'

test('happy', () => {
  const result = findLastIndex(x => x > 1)([1, 1, 1, 2, 3, 4, 1])
  expect(result).toBe(5)
  expect(findLastIndex(x => x === 0)([0, 1, 1, 2, 3, 4, 1])).toBe(0)
})
<summary>TypeScript test</summary>
import { findLastIndex, pipe } from 'rambda'

const list = [1, 2, 3]

describe('R.findLastIndex', () => {
  it('happy', () => {
    const predicate = (x: number) => x > 2
    const result = pipe(list, findLastIndex(predicate))
    result // $ExpectType number
  })
})

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

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

<summary>All TypeScript definitions</summary>
findNth<T>(predicate: (x: T) => boolean, nth: number): (list: T[]) => T | undefined;
<summary>R.findNth source</summary>
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++
    }
  }
}
<summary>Tests</summary>
import { findNth } from './findNth.js'

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

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

test('nothing is found', () => {
	const fn = x => 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

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

const duplicate = n => [n, n]

test('happy', () => {
  const fn = x => [x * 2]
  const list = [1, 2, 3]

  const result = flatMap(fn)(list)

  expect(result).toEqual([2, 4, 6])
})

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

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 => [[n]]
  expect(flatMap(nest)([1, 2, 3])).toEqual([[1], [2], [3]])
})

test('can compose', () => {
  function dec(x) {
    return [x - 1]
  }
  function times2(x) {
    return [x * 2]
  }

  const mdouble = flatMap(times2)
  const mdec = flatMap(dec)
  expect(mdec(mdouble([10, 20, 30]))).toEqual([19, 39, 59])
})
<summary>TypeScript test</summary>
import { flatMap, pipe } from 'rambda'

describe('R.flatMap', () => {
  it('happy', () => {
    const listOfLists: string[][] = [
      ['f', 'bar'],
      ['baz', 'b'],
    ]
    const result = pipe(
      listOfLists,
      x => x,
      flatMap(x => {
        x // $ExpectType string
        return Number(x) + 1
      }),
    )
    result // $ExpectType number[]
  })
})

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

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

<summary>All TypeScript definitions</summary>
flatten<T>(list: any[]): T[];
<summary>R.flatten source</summary>
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
}
<summary>Tests</summary>
import { flatten } from './flatten.js'

test('happy', () => {
  expect(flatten([1, 2, 3, [[[[[4]]]]]])).toEqual([1, 2, 3, 4])

  expect(flatten([1, [2, [[3]]], [4]])).toEqual([1, 2, 3, 4])

  expect(flatten([1, [2, [[[3]]]], [4]])).toEqual([1, 2, 3, 4])

  expect(flatten([1, 2, [3, 4], 5, [6, [7, 8, [9, [10, 11], 12]]]])).toEqual([
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
  ])
})

test('readme example', () => {
  const result = flatten([1, 2, [3, 30, [300]], [4]])
  expect(result).toEqual([1, 2, 3, 30, 300, 4])
})
<summary>TypeScript test</summary>
import { flatten, pipe } from 'rambda'

describe('flatten', () => {
  it('happy', () => {
    const result = pipe([1, 2, [3, [4]]], flatten<number>)
    result // $ExpectType number[]
  })
})

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

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

<summary>All TypeScript definitions</summary>
flattenObject<T extends object>(obj: T): FlattenObject<T>;
<summary>R.flattenObject source</summary>
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
}
<summary>Tests</summary>
import {
  flattenObject,
  flattenObjectHelper,
  transformFlatObject,
} from './flattenObject.js'

test('happy', () => {
  const obj = {
    c : 3,
    d : {
      'd.e' : [ 5, 6, 7 ],
      'd.z' : 4,
      'd.f' : { 'd.f.h' : 6 },
    },
  }
  const result = transformFlatObject(obj)
  expect(result).toEqual({
    'c'     : 3,
    'd.e'   : [ 5, 6, 7 ],
    'd.z'   : 4,
    'd.f.h' : 6,
  })
})

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,
    }
    expect(result).toEqual(expected)
})

test('flattenObjectHelper', () => {
  const result = flattenObjectHelper({
    a : 1,
    b : {
      c : 3,
      d : {
        e : 5,
        z : 4,
        f : { h : 6 },
      },
    },
  })
  const expected = {
    a : 1,
    b : {
      'b.c' : 3,
      'b.d' : {
        'b.d.e' : 5,
        'b.d.z' : 4,
        'b.d.f' : { 'b.d.f.h' : 6 },
      },
    },
  }
  expect(result).toEqual(expected)
})
<summary>TypeScript test</summary>
import { flattenObject, pipe } from 'rambda'

it('R.flattenObject', () => {
  const result = pipe({ a: { b: 1, c: 2 } }, flattenObject)
  result['a.b'] // $ExpectType number
  result['a.c'] // $ExpectType number
  // @ts-expect-error
  result['a.foo']
})

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

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

<summary>All TypeScript definitions</summary>
groupBy<T, K extends string = string>(fn: (x: T) => K): (list: T[]) => Partial<Record<K, T[]>>;
<summary>R.groupBy source</summary>
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)
}
<summary>Tests</summary>
import { groupBy } from './groupBy.js'

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 < 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 }
	);
})
<summary>TypeScript test</summary>
import { groupBy, pipe } from 'rambda'

describe('R.groupBy', () => {
  it('happy', () => {
    const groupByFn = (x: string) => String(x.length)
    const list = ['foo', 'bar']

    const result = pipe(list, groupBy(groupByFn))
    result // $ExpectType Partial<Record<string, string[]>>
  })
})

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

head


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

<summary>All TypeScript definitions</summary>
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;
<summary>R.head source</summary>
export function head(listOrString) {
  if (typeof listOrString === 'string') {
    return listOrString[0] || ''
  }

  return listOrString[0]
}
<summary>Tests</summary>
import { head } from './head.js'

test('head', () => {
  expect(head(['fi', 'fo', 'fum'])).toBe('fi')
  expect(head([])).toBeUndefined()
  expect(head('foo')).toBe('f')
  expect(head('')).toBe('')
})
<summary>TypeScript test</summary>
import { head, last } from 'rambda'

export const mixedList = [1, 'foo', 3, 'bar']
export const mixedListConst = [1, 'foo', 3, 'bar'] as const
export const numberList = [1, 2, 3]
export const numberListConst = [1, 2, 3] as const
export const emptyList = []
export const emptyString = ''
export const string = 'foo'

describe('R.head', () => {
  it('string', () => {
    head(string) // $ExpectType string
    last(string) // $ExpectType string
  })
  it('empty string', () => {
    head(emptyString) // $ExpectType string
    last(emptyString) // $ExpectType string
  })
  it('array', () => {
    head(numberList) // $ExpectType number
    head(numberListConst) // $ExpectType 1

    last(numberList) // $ExpectType number
    last(numberListConst) // $ExpectType 3
  })
  it('empty array', () => {
    const list = [] as const
    head(emptyList) // $ExpectType never
    head(list) // $ExpectType undefined
    last(emptyList) // $ExpectType never
    last(list) // $ExpectType undefined
  })

  it('mixed', () => {
    head(mixedList) // $ExpectType string | number
    head(mixedListConst) // $ExpectType 1
    last(mixedList) // $ExpectType string | number
    last(mixedListConst) // $ExpectType "bar"
  })
})

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

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

<summary>All TypeScript definitions</summary>
includes<T>(list: readonly T[]): (target: T) => boolean;
includes(list: readonly string[] | string): (substringToFind: string) => boolean;
<summary>R.includes source</summary>
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
  }
}
<summary>Tests</summary>
import { includes } from './includes.js'

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', () => {
  const result = includes([Number.NaN])(Number.NaN)
  expect(result).toBeTruthy()
})

test('with wrong input that does not throw', () => {
  const result = includes([1])(/foo/g)
  expect(result).toBeFalsy()
})
<summary>TypeScript test</summary>
import { pipe , includes} from 'rambda'

describe('R.includes', () => {
  it('happy', () => {
    const list = [{ a: { b: '1' } }, { a: { b: '2' } }, { a: { b: '3' } }]
    const result = pipe({ a: { b: '1' } }, includes(list))
    result // $ExpectType boolean
  })
  it('with string', () => {
    const result = pipe('oo', includes('foo'))
    result // $ExpectType boolean
  })
  it('with array of strings', () => {
		const result = pipe('1', includes(['1','2','3']))
    result // $ExpectType boolean
  })
})

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

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

<summary>All TypeScript definitions</summary>
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>;
<summary>R.indexBy source</summary>
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
	}
}
<summary>Tests</summary>
import { indexBy } from './indexBy.js'

test('happy', () => {
	const list = [{id: 'xyz', title: 'A'}, {id: 'abc', title: 'B'}]

	expect(
		indexBy('id')(list)
	).toEqual(
		{abc: {id: 'abc', title: 'B'}, xyz: {id: 'xyz', title: 'A'}}
	)
})
<summary>TypeScript test</summary>
import { pipe, indexBy } from 'rambda'

it('R.indexBy', () => {
	const list = [{id: 'xyz', title: 'A'}, {id: 'abc', title: 'B'}]
	const result = pipe(
		list,
		indexBy('id')
	)
	result.abc // $ExpectType { id: string; title: string; }
	result.foo // $ExpectType { id: string; title: string; }
})

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

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

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

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

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

test('will throw with bad input', () => {
  expect(() => indexOf([])(true)).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 })(listOfObjects)).toBe(-1)
  expect(indexOf({ c: 3 })(listOfObjects)).toBe(2)
})

test('list of arrays use R.equals', () => {
  const listOfLists = [[1], [2, 3], [2, 3, 4], [2, 3], [1], []]
  expect(indexOf([])(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)
})
<summary>TypeScript test</summary>
import { indexOf } from 'rambda'

describe('R.indexOf', () => {
  it('happy', () => {
    const list = [{ a: 1 }, { a: 2 }]
    const result = indexOf({ a: 1 })(list)
    result // $ExpectType number
  })
})

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

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

<summary>All TypeScript definitions</summary>
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;
<summary>R.init source</summary>
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) : []
}
<summary>Tests</summary>
import { init } from './init.js'

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

test('with string', () => {
  expect(init('foo')).toBe('fo')
  expect(init('f')).toBe('')
  expect(init('')).toBe('')
})
<summary>TypeScript test</summary>
import { map, pipe, init } from 'rambda'

describe('R.init', () => {
  it('with string', () => {
    const result = init('foo')

    result // $ExpectType string
  })
  it('with list - using const on short array', () => {
    const result = pipe(
      [1] as const,
      map(x => x * 2),
      init,
    )
    result // $ExpectType []
  })
  it('with list - using const on empty array', () => {
    const result = pipe(
      [] as const,
      map(x => x * 2),
      init,
    )
    result // $ExpectType []
  })
  it('with list - using const', () => {
    const result = pipe(
      [1, 2, 3] as const,
      map(x => x * 2),
      init,
    )
    result // $ExpectType [number, number]
  })
  it('with list - mixed types', () => {
    const result = init(['foo', 'bar', 1, 2, 3])

    result // $ExpectType (string | number)[]
  })
})

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

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

<summary>All TypeScript definitions</summary>
interpolate(inputWithTags: string): (templateArguments: object) => string;
<summary>R.interpolate source</summary>
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
  }
}
<summary>Tests</summary>
import { interpolate } from './interpolate.js'
import { pipe } from './pipe.js'

test('happy', () => {
  const result = pipe(
		{ name: 'John', age: 30 },
		interpolate('My name is {{name}} and I am {{age}} years old')
	)
	expect(result).toBe('My name is John and I am 30 years old')
})
<summary>TypeScript test</summary>
import { interpolate } from 'rambda'

const templateInput = 'foo {{x}} baz'
const templateArguments = { x: 'led zeppelin' }

it('R.interpolate', () => {
	const result = interpolate(templateInput)(templateArguments)

	result // $ExpectType string
})

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

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

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

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

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'])
})
<summary>TypeScript test</summary>
import { intersection } from 'rambda'

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

describe('R.intersection', () => {
  it('happy', () => {
    const result = intersection(list1)(list2)
    result // $ExpectType number[]
  })
})

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

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

<summary>All TypeScript definitions</summary>
intersectionWith<T1, T2>(
  pred: (a: T1, b: T2) => boolean,
  list1: T1[],
): (list2: T2[]) => T1[];
<summary>R.intersectionWith source</summary>
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)
}
<summary>Tests</summary>
import { intersectionWith } from './intersectionWith.js'

const a = {
  id: 1,
  name: 'a',
}
const b = {
  id: 2,
  name: 'b',
}
const c = {
  id: 3,
  name: 'c',
}
const f = (a, b) => intersectionWith((r, id) => r.id === id, a)(b)

test('only returns elements from the first list', () => {
  expect(f([a, b, c], [])).toEqual([])
  expect(f([a, b, c], [1])).toEqual([a])
  expect(f([a, b, c], [1, 2])).toEqual([a, b])
  expect(f([a, b, c], [1, 2, 3])).toEqual([a, b, c])
  expect(f([a, b, c], [1, 2, 3, 4])).toEqual([a, b, c])
})

test('does not remove duplicates', () => {
  expect(f([a, a, a], [1, 2, 3])).toEqual([a, a, a])
  expect(f([a, b, c], [1, 1, 1])).toEqual([a])
})

test('readme example', () => {
  const list1 = [1, 2, 3, 4, 5]
  const list2 = [4, 5, 6]
  const predicate = (x, y) => x >= y
  const result = intersectionWith(predicate, list1)(list2)
  expect(result).toEqual([4, 5])
})
<summary>TypeScript test</summary>
import { intersectionWith, pipe } from 'rambda'

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

describe('R.intersectionWith', () => {
  it('happy', () => {
    const result = pipe(
      list1,
      intersectionWith((x, y) => x === y, list2),
    )
    result // $ExpectType number[]
  })
})

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

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

<summary>All TypeScript definitions</summary>
intersperse<T>(separator: T): (list: T[]) => T[];
<summary>R.intersperse source</summary>
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
  }
}
<summary>Tests</summary>
import { intersperse } from './intersperse.js'

test('intersperse', () => {
  const list = [{ id: 1 }, { id: 2 }, { id: 10 }, { id: 'a' }]
  expect(intersperse('!')(list)).toEqual([
    { id: 1 },
    '!',
    { id: 2 },
    '!',
    { id: 10 },
    '!',
    { id: 'a' },
  ])

  expect(intersperse('!')([])).toEqual([])
})
<summary>TypeScript test</summary>
import { intersperse } from 'rambda'

describe('R.intersperse', () => {
  it('curried', () => {
    const result = intersperse('|')(['foo', 'bar'])
    result // $ExpectType string[]
  })
})

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

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

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

it('R.join', () => {
  const result = pipe([1, 2, 3], join('|'))
  result // $ExpectType string
})

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

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

<summary>All TypeScript definitions</summary>
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;
<summary>R.last source</summary>
export function last(listOrString) {
  if (typeof listOrString === 'string') {
    return listOrString[listOrString.length - 1] || ''
  }

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

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

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

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

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

test('will throw with bad input', () => {
  expect(() => indexOf([])(true)).toThrowError('indexOf is not defined')
})

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 })(listOfObjects)).toBe(-1)
  expect(lastIndexOf({ c: 3 })(listOfObjects)).toBe(2)
})

test('list of arrays uses R.equals', () => {
  const listOfLists = [[1], [2, 3], [2, 3, 4], [2, 3], [1], []]
  expect(lastIndexOf([])(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)
})
<summary>TypeScript test</summary>
import { lastIndexOf, pipe } from 'rambda'

describe('R.lastIndexOf', () => {
  const result = pipe([{ a: 1 }, { a: 2 }, { a: 3 }], lastIndexOf({ a: 2 }))
  result // $ExpectType number
})

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

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.

:boom: 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

<summary>All TypeScript definitions</summary>
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>;
<summary>R.map source</summary>
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)
}
<summary>Tests</summary>
import { map } from './map.js'

const double = x => x * 2

it('happy', () => {
  expect(map(double)([1, 2, 3])).toEqual([2, 4, 6])
})
<summary>TypeScript test</summary>
import { map, pipe } from 'rambda'

const list = [1, 2, 3]

it('R.map', () => {
  const result = pipe(
    list,
    x => x,
    map(x => {
      x // $ExpectType number
      return String(x)
    }),
  )
  result // $ExpectType string[]
})

it('R.map - index in functor', () => {
  const result = pipe(
    list,
    x => x,
    map((x, i) => {
      x // $ExpectType number
      i // $ExpectType number
      return String(x)
    }),
  )
  result // $ExpectType string[]
})

it('R.map - without pipe', () => {
  map(x => {
    x // $ExpectType unknown
  })([1, 2, 3])
})

it('R.map - without pipe but explicitly typed', () => {
  const result = map<number[], string>(x => {
    x // $ExpectType number
    return String(x)
  })([1, 2, 3])
  result // $ExpectType string[]
})

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

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

<summary>All TypeScript definitions</summary>
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>>;
<summary>R.mapAsync source</summary>
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
  }
}
<summary>Tests</summary>
import { delay } from './delay.js'
import { map } from './map.js'
import { mapAsync } from './mapAsync.js'
import { pipeAsync } from './pipeAsync.js'

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

test('happy', async () => {
  const indexes = []
  const fn = async (x, prop) => {
    await delay(100)
    indexes.push(prop)
    return x + 1
  }
  const result = await mapAsync(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 => x + 1
  const result = await pipeAsync(
    [1, 2, 3],
    map(x => x + 1),
    mapAsync(async x => {
      delay(x)

      return x
    }),
		mapAsync(fn),
    map(x => x * 10),
  )
  expect(result).toEqual([30, 40, 50])
})

test('error', async () => {
  try {
    await mapAsync(rejectDelay)([1, 2, 3])
  } catch (err) {
    expect(err).toBe(21)
  }
})
<summary>TypeScript test</summary>
import { mapAsync, pipeAsync, map } from 'rambda'

const list = ['a', 'bc', 'def']
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

it('R.mapAsync', async () => {
  const result = await pipeAsync(
    list,
    mapAsync(async x => {
      await delay(100)
      x // $ExpectType string
      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
    }),
  )
  result // $ExpectType number[]
})

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

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

<summary>All TypeScript definitions</summary>
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>;
...
...
<summary>R.mapChain source</summary>
import { mapFn } from './map.js';

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

const double = x => x * 2

it('happy', () => {
  expect(mapChain(double, double, double)([1, 2, 3])).toEqual([8, 16, 24])
})
<summary>TypeScript test</summary>
import { mapChain, pipe } from 'rambda'

const list = [1, 2, 3]

it('R.mapChain', () => {
  const result = pipe(
    list,
    mapChain(
      x => {
        x // $ExpectType number
        return String(x)
      },
      x => {
        x // $ExpectType string
        return x !== 'foo'
      },
    ),
  )
  result // $ExpectType boolean[]
})

it('R.mapChain - with index', () => {
  const result = pipe(
    list,
    mapChain(
      x => {
        x // $ExpectType number
        return String(x)
      },
      (x, i) => {
        i // $ExpectType number
        x // $ExpectType string
        return x !== 'foo'
      },
    ),
  )
  result // $ExpectType boolean[]
})

it('R.mapChain - 3 functions', () => {
  const result = pipe(
    list,
    x => x,
    mapChain(
      x => {
        x // $ExpectType number
        return String(x)
      },
      x => {
        x // $ExpectType string
        return x !== 'foo'
      },
      x => {
        x // $ExpectType boolean
        return x ? 'foo' : 'bar'
      },
    ),
  )
  result // $ExpectType ("foo" | "bar")[]
})

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

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

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

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

		return willReturn
	}
}
<summary>Tests</summary>
import { mapKeys } from './mapKeys.js'

test('happy', () => {
	const result = mapKeys((prop, x) => `${ prop }-${x}`)({a:1, b: 2 })
	const expected = { 'a-1': 1, 'b-2': 2 }

	expect(result).toEqual(expected)
})
<summary>TypeScript test</summary>
import { mapKeys, pipe } from 'rambda'

it('R.mapKeys', () => {
  const result = pipe(
    { a: 1, b: 2 },
    mapKeys((prop, x) => `${prop}-${x}`),
    mapKeys(prop => `${prop}-${prop}`),
  )
  result // $ExpectType Record<string, number>
})

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

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

<summary>All TypeScript definitions</summary>
mapObject<T extends object, Value>(
  valueMapper: (
    value: EnumerableStringKeyedValueOf<T>,
    key: EnumerableStringKeyOf<T>,
    data: T,
  ) => Value,
): (data: T) => MappedValues<T, Value>;
<summary>R.mapObject source</summary>
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
  }
}
<summary>Tests</summary>
import { mapObject } from './mapObject.js'

const double = x => x * 2

it('happy', () => {
  expect(mapObject(double)({ a: 1, b: 2, c: 3 })).toEqual({ a: 2, b: 4, c: 6 })
})
<summary>TypeScript test</summary>
import { mapObject, pipe } from 'rambda'

describe('R.mapObject', () => {
  it('iterable with one arguments', () => {
    const result = pipe(
      { a: 1 },
      mapObject(a => {
        a // $ExpectType number
        return `${a}`
      }),
    )

    result // $ExpectType { a: string; }
  })
  it('iterable with one arguments', () => {
    const result = pipe(
      { a: [1,2,3], b: 'foo' },
      mapObject(a => {
        a // $ExpectType string | number[]
        return typeof a as string
      }),
    )

    result // $ExpectType { a: string; b: string; }
  })
  it('iterable with two arguments', () => {
    const result = pipe(
      { a: 1, b: 'foo' },
      mapObject((a, b) => {
        a // $ExpectType string | number
        b // $ExpectType "a" | "b"
        return `${a}`
      }),
    )

    result // $ExpectType { a: string; b: string; }
  })
  it('iterable with three arguments', () => {
    const result = pipe(
      { a: 1, b: 'foo' },
      mapObject((a, b, c) => {
        a // $ExpectType string | number
        b // $ExpectType "a" | "b"
        c // $ExpectType { a: number; b: string; }
        return `${a}`
      }),
    )

    result // $ExpectType { a: string; b: string; }
  })
})

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

mapObjectAsync


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

    return willReturn
  }
}
<summary>Tests</summary>
import { delay } from './delay.js'
import { mapObjectAsync } from './mapObjectAsync.js'
import { pipeAsync } from './pipeAsync.js'

test('happy', async () => {
  const indexes = []
  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,
  })
})
<summary>TypeScript test</summary>
import { mapObjectAsync, pipeAsync } from 'rambda'
import { delay } from 'rambdax'

it('R.mapObjectAsync', async () => {
  const result = await pipeAsync(
    { a: 'foo', b: 'bar' },
    mapObjectAsync(async x => {
      await delay(100)
      x // $ExpectType string
      return x.length % 2 ? x.length + 1 : x.length + 10
    }),
    x => x,
    mapObjectAsync(async x => {
      await delay(100)
      return x + 1
    }),
  )
  result.a // $ExpectType number
  result.b // $ExpectType number
})

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

mapParallelAsync


mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[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.

<summary>All TypeScript definitions</summary>
mapParallelAsync<T extends IterableContainer, U>(
  fn: (value: T[number]) => Promise<U>,
	batchSize?: number,
): (data: T) => Promise<Mapped<T, U>>;
<summary>R.mapParallelAsync source</summary>
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
	}	
}
<summary>Tests</summary>
import { pipeAsync } from './pipeAsync.js'
import { delay } from './delay.js'
import { mapParallelAsync } from './mapParallelAsync.js'

test('happy', async () => {
  const fn = async (x, i) => {
    await delay(100)

    return x + i
  }
  const result = await mapParallelAsync(fn)([ 1, 2, 3 ])
  expect(result).toEqual([ 1, 3, 5 ])
})

test('pipeAsync', async () => {
  const result = await pipeAsync(
		[1, 2, 3],
    mapParallelAsync(async x => {
      await delay(100)

      return x + 1
    })
	)
  expect(result).toEqual([ 2,3,4 ])
})

test('with batchSize', async () => {
	const fn = async (x, i) => {
		await delay(100)
		return `${x}:${i}`
	}
	const result = await mapParallelAsync(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

<summary>All TypeScript definitions</summary>
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;
<summary>R.mapPropObject source</summary>
export function mapPropObject(fn, prop) {
  return obj => {
		if (!Array.isArray(obj[prop])) return obj
			
			return {
				...obj,
				[prop]: obj[prop].map(fn)
			}
		}
 	 }
<summary>Tests</summary>
import { mapPropObject } from './mapPropObject.js'
import { pipe } from './pipe.js'

it('happy', () => {
  const result = pipe(
    { a: [1, 2, 3], b: 'foo' },
    mapPropObject(x => ({ a: x, flag: x > 2 }), 'a'),
  )

  expect(result).toEqual({
    a: [
      { a: 1, flag: false },
      { a: 2, flag: false },
      { a: 3, flag: true },
    ],
    b: 'foo',
  })
})
<summary>TypeScript test</summary>
import {  map, mapPropObject, pipe } from 'rambda'

describe('R.mapPropObject', () => {
  it('iterable with one arguments', () => {
    const result = pipe(
      { a: [1,2,3], b: 'foo' },
      mapPropObject('a', x => {
        x // $ExpectType number
        return {
          a: x,
          flag: x > 2,
        }
      }),
    )

    result.a // $ExpectType { a: number; flag: boolean; }[]
		result.b // $ExpectType string
  })

  it('iterable with two arguments', () => {
    const result = pipe(
      { a: [1,2,3], b: 'foo' },
      mapPropObject('a', (x, list) => {
        x // $ExpectType number
        list // $ExpectType number[]
				return list.length
      }),
    )
		result.a // $ExpectType number[]
		result.b // $ExpectType string
  })

  it('more complex example', () => {
    const result = pipe(
      [{a:[true, false, true], b: 'foo'}],
      map(
					mapPropObject( 'a',(a) => {
						a // $ExpectType boolean
						return {a, b: 2}
								})
					)
			)

    result[0].a[0].a // $ExpectType boolean
    result[0].a[0].b // $ExpectType number
  })
})

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

match


match(regExpression: RegExp): (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

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

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

test('happy', () => {
  expect(match(/a./g)('foo bar baz')).toEqual(['ar', 'az'])
})

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

test('with string', () => {
  expect(match('a')('foo')).toEqual([])
})
<summary>TypeScript test</summary>
import { match } from 'rambda'

const str = 'foo bar'

describe('R.match', () => {
  it('happy', () => {
    const result = match(/foo/)(str)
    result // $ExpectType string[]
  })
})

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

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

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

test('happy', () => {
  expect(maxBy(Math.abs, 2)(-5)).toBe(-5)
  expect(maxBy(Math.abs, -5)(2)).toBe(-5)
})
<summary>TypeScript test</summary>
import { maxBy, pipe } from 'rambda'

const first = 1
const second = 2

it('R.maxBy', () => {
  const result = pipe(
    second,
    maxBy(x => (x % 2 === 0 ? 1 : -1), first),
  )
  result // $ExpectType number
})

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

merge


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

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

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

const obj = {
  foo: 1,
  bar: 2,
}

test('happy', () => {
  expect(merge(obj)({ bar: 20 })).toEqual({
    foo: 1,
    bar: 20,
  })
})
<summary>TypeScript test</summary>
import { merge, mergeTypes, pipe } from 'rambda'

it('R.merge', () => {
  const result = pipe({ foo: 1 }, merge({ bar: 2 }), mergeTypes)
  result.foo // $ExpectType number
  result.bar // $ExpectType number
})

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

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.

<summary>All TypeScript definitions</summary>
mergeTypes<T>(x: T): MergeTypes<T>;
<summary>R.mergeTypes source</summary>
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

<summary>All TypeScript definitions</summary>
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;
<summary>R.middle source</summary>
import { init } from './init.js'
import { tail } from './tail.js'

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

test('middle', () => {
  expect(middle([1, 2, 3])).toEqual([2])
  expect(middle([1, 2])).toEqual([])
  expect(middle([1])).toEqual([])
  expect(middle([])).toEqual([])

  expect(middle('abc')).toBe('b')
  expect(middle('ab')).toBe('')
  expect(middle('a')).toBe('')
  expect(middle('')).toBe('')
})
<summary>TypeScript test</summary>
import { map, middle, pipe } from 'rambda'

describe('R.middle', () => {
  it('with string', () => {
    const result = middle('foo')

    result // $ExpectType string
  })
  it('with list - using const on short array', () => {
    const result = pipe(
      [1, 2] as const,
      map(x => x * 2),
      middle,
    )
    result // $ExpectType []
  })
  it('with list - using const on empty array', () => {
    const result = pipe(
      [] as const,
      map(x => x * 2),
      middle,
    )
    result // $ExpectType []
  })
  it('with list - using const', () => {
    const result = pipe(
      [1, 2, 3, 4] as const,
      map(x => x * 2),
      middle,
    )
    result // $ExpectType [number, number]
  })
  it('with list - mixed types', () => {
    const result = middle(['foo', 'bar', 1, 2, 3])

    result // $ExpectType (string | number)[]
  })
})

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

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

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

test('happy', () => {
  expect(minBy(Math.abs, -5)(2)).toBe(2)
  expect(minBy(Math.abs, 2)(-5)).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

<summary>All TypeScript definitions</summary>
modifyItemAtIndex<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
<summary>R.modifyItemAtIndex source</summary>
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
  }
}
<summary>Tests</summary>
import { modifyItemAtIndex } from './modifyItemAtIndex.js'

const add10 = x => x + 10

const list = [0, 1, 2]
const expected = [0, 11, 2]

test('happy', () => {
  expect(modifyItemAtIndex(1, add10)(list)).toEqual(expected)
})

test('with negative index', () => {
  expect(modifyItemAtIndex(-2, add10)(list)).toEqual(expected)
})

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

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

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

<summary>All TypeScript definitions</summary>
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>;
...
...
<summary>R.modifyPath source</summary>
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)
}
<summary>Tests</summary>
import { modifyPath } from './modifyPath.js'

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

test('happy', () => {
  const result = modifyPath('a.b.c', x => x + 1)(obj)
  expect(result).toEqual({ a: { b: { c: 2 } } })
})

test('works only on existing paths', () => {
  const result = modifyPath('a.b.d', x => x + 1)(obj)
  expect(result).toEqual(obj)
})
<summary>TypeScript test</summary>
import { modifyPath, pipe } from 'rambda'

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

describe('R.modifyPath', () => {
  it('array path', () => {
    const result = pipe(
      obj,
      modifyPath(['a', 'b', 'c'], (x: number) => String(x)),
    )
    result.a.b.c // $ExpectType string
  })
  it('string path', () => {
    const result = pipe(
      obj,
      modifyPath('a.b.c', (x: number) => String(x)),
    )
    result.a.b.c // $ExpectType string
  })
})

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

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

<summary>All TypeScript definitions</summary>
modifyProp<T, K extends keyof T>(
  prop: K,
  fn: (x: T[K]) => T[K],
): (target: T) => T;
<summary>R.modifyProp source</summary>
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)
}
<summary>Tests</summary>
import { modifyProp } from './modifyProp.js'

const person = {
  name: 'foo',
  age: 20,
}

test('happy', () => {
  expect(modifyProp('age', x => x + 1)(person)).toEqual({
    name: 'foo',
    age: 21,
  })
})

test('property is missing', () => {
  expect(modifyProp('foo', x => x + 1)(person)).toEqual(person)
})

test('adjust if `array` at the given key with the `transformation` function', () => {
  expect(modifyProp(1, x => x + 1)([100, 1400])).toEqual([100, 1401])
})
<summary>TypeScript test</summary>
import { modifyProp, pipe } from 'rambda'

it('R.modify', () => {
  const result = pipe(
    { a: 1, b: 2, c: { d: 3 } },
    modifyProp('a', val => val + 1),
  )
  result // $ExpectType { a: number; b: number; c: { d: number; }; }

  pipe(
    { a: 1, b: 2, c: { d: 3 } },
    // @ts-expect-error
    modifyProp('ax', val => val + 1),
  )

  pipe(
    { a: 1, b: 2, c: { d: 3 } },
    // @ts-expect-error
    modifyProp('a', val => String(val)),
  )
})

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

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

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

    return true
  }
}
<summary>Tests</summary>
import { none } from './none.js'

const isEven = n => n % 2 === 0

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

test('when false', () => {
  expect(none(input => input > 1)([1, 2, 3])).toBeFalsy()
})
<summary>TypeScript test</summary>
import { none, pipe } from 'rambda'

describe('R.none', () => {
  it('happy', () => {
    const result = pipe(
      [1, 2, 3],
      none(x => x > 0),
    )
    result // $ExpectType boolean
  })
})

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

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

<summary>All TypeScript definitions</summary>
objectIncludes<T>(specification: T): (obj: Partial<T>) => boolean;
<summary>R.objectIncludes source</summary>
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
  }
}
<summary>Tests</summary>
import { objectIncludes } from './objectIncludes.js'

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

  const result = objectIncludes(condition)(input)
  const expectedResult = true

  expect(result).toEqual(expectedResult)
})

test('when false', () => {
  const condition = { a: 1 }
  const input = { b: 2 }

  const result = objectIncludes(condition)(input)
  const expectedResult = false

  expect(result).toEqual(expectedResult)
})

test('with nested object', () => {
  const condition = { a: { b: 1 } }
  const input = {
    a: { b: 1 },
    c: 2,
  }

  const result = objectIncludes(condition)(input)
  const expectedResult = true

  expect(result).toEqual(expectedResult)
})

test('with wrong input', () => {
  const condition = { a: { b: 1 } }

  expect(() => objectIncludes(condition)(null)).toThrowErrorMatchingInlineSnapshot(
    `[TypeError: Cannot read properties of null (reading 'a')]`,
  )
})
<summary>TypeScript test</summary>
import { objectIncludes, pipe } from 'rambda'

describe('R.objectIncludes', () => {
  it('happy', () => {
    const result = pipe({ a: 1, b: 2, c: { d: 3 } }, objectIncludes({ a: 2 }))
    result // $ExpectType boolean
  })
  it('nested', () => {
    const result = pipe({ a: 1, b: 2, c: { d: 3 } }, objectIncludes({ c: { d: 3 } }))
    result // $ExpectType boolean
  })
})

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

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

<summary>All TypeScript definitions</summary>
objOf<T, K extends PropertyKey>(key: K): (value: T) => { [P in K]: T };
<summary>R.objOf source</summary>
export function objOf(key) {
  return value => ({ [key]: value })
}
<summary>Tests</summary>
import { objOf } from './objOf.js'

test('happy', () => {
  expect(objOf('foo')(42)).toEqual({ foo: 42 })
})
<summary>TypeScript test</summary>
import { objOf, pipe } from 'rambda'

const key = 'foo'
const value = 42

it('R.objOf', () => {
  const result = pipe(value, objOf(key))
  result.foo // $ExpectType number
  // @ts-expect-error
  result.bar
})

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

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

<summary>All TypeScript definitions</summary>
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;
<summary>R.omit source</summary>
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
  }
}
<summary>Tests</summary>
import { omit } from './omit.js'

test('with string as condition', () => {
  const obj = {
    a: 1,
    b: 2,
    c: 3,
  }
  const result = omit('a,c')(obj)
  const expectedResult = { b: 2 }

  expect(result).toEqual(expectedResult)
})

test('with array as condition', () => {
  expect(
    omit(['a', 'c', 'd'])({
      a: 'foo',
      b: 'bar',
      c: 'baz',
    }),
  ).toEqual({ b: 'bar' })
})
<summary>TypeScript test</summary>
import { omit, pipe } from 'rambda'

const input = { a: 'foo', b: 2, c: 3 }

describe('R.omit', () => {
  it('with string as input', () => {
    const result = pipe(input, omit('a,b'))
    result.c // $ExpectType number
  })
  it('with array as input', () => {
    const result = pipe(input, omit(['a', 'b']))
    result.c // $ExpectType number
  })
})

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

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

<summary>All TypeScript definitions</summary>
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>];
<summary>R.partition source</summary>
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]
  }
}
<summary>Tests</summary>
import { partition } from './partition.js'

test('happy', () => {
  const list = [1, 2, 3]
  const predicate = x => x > 2

  const result = partition(predicate)(list)
  expect(result).toEqual([[3], [1, 2]])
})
<summary>TypeScript test</summary>
import { partition, pipe } from 'rambda'

describe('R.partition', () => {
  it('happy', () => {
    const predicate = (x: number) => {
      return x > 2
    }
    const list = [1, 2, 3, 4]

    const result = pipe(list, partition(predicate))
    result // $ExpectType [number[], number[]]
  })
  it('with simple object', () => {
    const result = pipe(
      [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }],
      partition(x => x.a > 2),
    )
    result // $ExpectType [{ a: number; }[], { a: number; }[]]
  })
  it('with complex object', () => {
    interface Foo {
      a: number
    }
    interface Bar {
      b: number
    }
    const list1: (Foo | Bar)[] = [{ a: 1 }, { b: 2 }, { a: 3 }, { b: 4 }]
    const filterFoo = (x: Foo | Bar): x is Foo => 'a' in x
    const result = pipe(list1, partition(filterFoo))
    result // $ExpectType [Foo[], Bar[]]
  })
})

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

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

<summary>All TypeScript definitions</summary>
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>];
<summary>R.partitionObject source</summary>
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]
}
}
<summary>Tests</summary>
import { partitionObject } from './partitionObject.js'

test('happy', () => {
  const predicate = (value, prop) => {
    expect(typeof prop).toBe('string')

    return value > 2
  }
  const hash = {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
  }

  const result = partitionObject(predicate)(hash)
  const expectedResult = [
    {
      c: 3,
      d: 4,
    },
    {
      a: 1,
      b: 2,
    },
  ]

  expect(result).toEqual(expectedResult)
})
<summary>TypeScript test</summary>
import { partitionObject, pipe } from 'rambda'

describe('R.partition', () => {
  it('happy', () => {
		let result = pipe(
			{ a: 1, b: 2 },
			partitionObject((x, prop) => x> 1 || prop === 'c'),
		)
    result // $ExpectType [Record<string, number>, Record<string, number>]
  })
  it('with complex object', () => {
    interface Foo {
      a: number
    }
    interface Bar {
      b: number
    }
    const obj: Record<string, (Foo | Bar)> = {
			a: { a: 1 },
			b: { b: 2 },
			c: { a: 3 },
			d: { b: 4 },
		}
    const filterFoo = (x: Foo | Bar): x is Foo => 'a' in x
    const result = pipe(obj, partitionObject(filterFoo))
    result // $ExpectType [Record<string, Foo>, Record<string, Bar>]
  })
})

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

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.

:boom: 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

<summary>All TypeScript definitions</summary>
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];
...
...
<summary>R.path source</summary>
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
	}
}
<summary>Tests</summary>
import { path } from './path.js'

test('with array inside object', () => {
  const obj = { a: { b: [1, { c: 1 }] } }

  expect(path('a.b.1.c')(obj)).toBe(1)
})

test('works with undefined', () => {
  const obj = { a: { b: { c: 1 } } }

  expect(path('a.b.c.d.f')(obj)).toBeUndefined()
  expect(path('foo.babaz')(undefined)).toBeUndefined()
  expect(path('foo.babaz')(undefined)).toBeUndefined()
})

test('works with string instead of array', () => {
  expect(path('foo.bar.baz')({ foo: { bar: { baz: 'yes' } } })).toBe('yes')
})

test('path', () => {
  expect(path(['foo', 'bar', 'baz'])({ foo: { bar: { baz: 'yes' } } })).toBe('yes')
  expect(path(['foo', 'bar', 'baz'])(null)).toBeUndefined()
  expect(path(['foo', 'bar', 'baz'])({ foo: { bar: 'baz' } })).toBeUndefined()
})

test('with number string in between', () => {
  expect(path(['a', '1', 'b'])({ a: [{ b: 1 }, { b: 2 }] })).toBe(2)
})

test('null is not a valid path', () => {
  expect(
    path('audio_tracks')({
      a: 1,
      audio_tracks: null,
    }),
  ).toBeUndefined()
})
<summary>TypeScript test</summary>
import { path, pipe } from 'rambda'

const input = { a: { b: { c: true } } }

describe('R.path with string as path', () => {
  it('happy', () => {
    const result = pipe(input, path(['a', 'b']))
    const resultStringInput = pipe(input, path('a.b.c'))
    result.c // $ExpectType boolean
    resultStringInput // $ExpectType boolean
  })
  it('happy', () => {
    const result = pipe([1, 2, 3], path([1]))
    result // $ExpectType number
  })
})

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

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

<summary>All TypeScript definitions</summary>
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;
...
...
<summary>R.pathSatisfies source</summary>
import { path } from './path.js'

export function pathSatisfies(fn, pathInput) {
  return obj => Boolean(fn(path(pathInput)(obj)))
}
<summary>Tests</summary>
import { pathSatisfies } from './pathSatisfies.js'

const isPositive = n => n > 0

it('returns true if the specified object path satisfies the given predicate', () => {
  expect(pathSatisfies(isPositive, ['x', 'y'])({ x: { y: 1 } })).toBe(true)
})

it('returns false if the specified path does not exist', () => {
  expect(pathSatisfies(isPositive, ['x', 'y'])({ x: { z: 42 } })).toBe(false)
  expect(pathSatisfies(isPositive, 'x.y')({ x: { z: 42 } })).toBe(false)
})

it('returns false otherwise', () => {
  expect(pathSatisfies(isPositive, ['x', 'y'])({ x: { y: 0 } })).toBe(false)
})
<summary>TypeScript test</summary>
import { pathSatisfies, pipe } from 'rambda'

const input = { a: { b: { c: 'bar' } } }

describe('R.pathSatisfies', () => {
  it('happy', () => {
    const result = pipe(
      input,
      pathSatisfies(
        x => {
          x // $ExpectType string
          return x !== 'foo'
        },
        ['a', 'b', 'c'],
      ),
    )
    const resultStringInput = pipe(
      input,
      pathSatisfies(x => {
        x // $ExpectType string
        return x !== 'foo'
      }, 'a.b.c'),
    )
    result // $ExpectType boolean
    resultStringInput // $ExpectType boolean
  })
})

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

permutations


permutations<T>(list: T[]): T[][]
const result = R.permutations(
	[1, 2]
)
// => [[1, 2], [2, 1]]

Try this R.permutations example in Rambda REPL

<summary>All TypeScript definitions</summary>
permutations<T>(list: T[]): T[][];
<summary>R.permutations source</summary>
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.

:boom: 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

<summary>All TypeScript definitions</summary>
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;
<summary>R.pick source</summary>
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
  }
}
<summary>Tests</summary>
import { pick } from './pick.js'

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

test('props to pick is a string', () => {
  const result = pick('a,c')(obj)
  const expectedResult = {
    a: 1,
    c: 3,
  }

  expect(result).toEqual(expectedResult)
})

test('when prop is missing', () => {
  const result = pick('a,d,f')(obj)
  expect(result).toEqual({ a: 1 })
})

test('props to pick is an array', () => {
  expect(
    pick(['a', 'c'])({
      a: 'foo',
      b: 'bar',
    }),
  ).toEqual({
    a: 'foo',
  })
})
<summary>TypeScript test</summary>
import { pick, pipe } from 'rambda'

const input = { a: 'foo', c: 3 }

describe('R.pick', () => {
  it('with string as input', () => {
    const result = pipe(input, pick('a,c'))
    result.a // $ExpectType string
    result.c // $ExpectType number
  })
  it('with array as input', () => {
		const result = pipe(input, pick(['a', 'c']))
    result.a // $ExpectType string
    result.c // $ExpectType number
  })
	it('throws error if some keys do not exist', () => {
		// @ts-expect-error
		pipe(input, pick('a,c,b,o'))
	})
})

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

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

<summary>All TypeScript definitions</summary>
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;
...
...
<summary>R.pipe source</summary>
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)
}
<summary>Tests</summary>
import { filter } from './filter.js'
import { map } from './map.js'
import { pipe } from './pipe.js'

test('happy', () => {
  const result = pipe(
    [1, 2, 3],
    filter(x => x > 1),
    map(x => x * 10),
    map(x => x + 1),
  )
  const expectedResult = [21, 31]

  expect(result).toEqual(expectedResult)
})
<summary>TypeScript test</summary>
import {
  type MergeTypes,
  append,
  assertType,
  defaultTo,
  drop,
  dropLast,
  evolve,
  filter,
  find,
  head,
  map,
  pick,
  pipe,
  split,
  union,
} from 'rambda'
type IsNotNever<T> = [T] extends [never] ? false : true
type Expect<T extends true> = T

interface BaseBook {
  title: string
  year: number
  description?: string
  userRating?: number
}
interface Book extends BaseBook {
  awards: {
    number: number
    years?: number[]
  }
  status?: Status
}
interface BookWithBookmarkStatus extends Book {
  bookmarkFlag: boolean
}
interface BookWithReadStatus extends Book {
  readFlag: boolean
}
type BookToRead = BookWithBookmarkStatus & BookWithReadStatus
type FamousBook = Book & {
	status: 'famous'
}

const checkIfFamous = (x: Book): x is FamousBook => {
	return x.status === 'famous'
}
const zaratustra: BaseBook = {
  title: 'Zaratustra',
  year: 1956,
}
const brothersKaramazov = {
  title: 'Brothers Karamazov',
  year: 1880,
}

const awardedZaratustra: Book = {
  ...zaratustra,
  awards: {
    number: 1,
    years: [1956],
  },
}
const awardedBrothersKaramazov: Book = {
  ...brothersKaramazov,
  awards: {
    number: 2,
    years: [1869, 1870],
  },
}
const awardedZaratustraToRead: BookToRead = {
  ...awardedZaratustra,
  readFlag: true,
  bookmarkFlag: true,
}
const awardedBaseValue: Book = {
  title: '',
  year: 0,
  awards: {
    number: 0,
    years: [],
  },
}

type Status = 'famous' | 'can be skipped' | 'must-read'

function checkBookToRead(x: Book): x is BookToRead {
  return (x as BookToRead).readFlag && (x as BookToRead).bookmarkFlag
}

function tapFn<T, U>(
  transformFn: (x: T) => U,
  fn: (a: T, b: U) => void,
): (x: T) => T {
  return x => {
    const result = transformFn(x)
    fn(x, result)
    return x
  }
}

function simplify<T>(x: T) {
  return x as MergeTypes<T>
}

describe('real use cases - books', () => {
  it('case 1', () => {
    const result = pipe(
      [awardedZaratustra, awardedBrothersKaramazov],
      filter(checkIfFamous),
      drop(1),
      // without converting to `as FamousBook`, endsWith will pick up `Book` as type
      tapFn(union([awardedBrothersKaramazov]), (a, b) => {
        a // $ExpectType Book[]
        b // $ExpectType Book[]
      }),
      find(x => {
        x // $ExpectType Book
        return x.title === 'Brothers Karamazov'
      }),
      x => [x],
      filter(Boolean),
    )
    const final: Expect<IsNotNever<typeof result>> = true
  })
  it('case 2', () => {
    const getResult = (book: BaseBook) =>
      pipe(
        book,
        defaultTo(awardedBaseValue),
        assertType(checkBookToRead),
        x => [x],
        dropLast(1),
        append(awardedZaratustraToRead),
        head,
        evolve({
          year: x => x + 1,
        }),
        simplify,
        pick('year'),
      )
    const result = getResult(zaratustra)
    const final: Expect<IsNotNever<typeof result>> = true
  })
  it('case 3', () => {
    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(',')))
    result // $ExpectType string[][]
  })
})

it('R.pipe', () => {
  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' }),
  )

  result.a // $ExpectType number
  result.b // $ExpectType string
  result.c // $ExpectType string
})

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

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

<summary>All TypeScript definitions</summary>
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;
...
...
<summary>R.pipeAsync source</summary>
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
}
<summary>Tests</summary>
import { delay } from './delay.js'
import { pipeAsync } from './pipeAsync.js'

const fn1 = x => {
  return new Promise(resolve => {
    resolve(x + 2)
  })
}
const fn2 = async x => {
  await delay(1)

  return x + 3
}

test('happy', async () => {
  const result = await pipeAsync(1, fn1, x => x + 2, fn2)
  expect(result).toBe(8)
})
<summary>TypeScript test</summary>
import { pipeAsync } from 'rambda'
import { delay } from 'rambdax'

describe('R.pipeAsync', () => {
  it('happy', async () => {
    const result = await pipeAsync(
      4,
      async x => {
        x // $ExpectType number
        await delay(100)
        return x + 1
      },
      x => {
        x // $ExpectType number
        return Promise.resolve([x])
      },
    )

    result // $ExpectType number[]
  })
})

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

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

:boom: 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

<summary>All TypeScript definitions</summary>
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;
};
...
...
<summary>R.pluck source</summary>
export function pluck(property) {
  return list => {
    const willReturn = []

    list.forEach(x => {
      if (x[property] !== undefined) {
        willReturn.push(x[property])
      }
    })

    return willReturn
  }
}
<summary>Tests</summary>
import { pluck } from './pluck.js'

test('happy', () => {
  expect(pluck('a')([{ a: 1 }, { a: 2 }, { b: 1 }])).toEqual([1, 2])
})

test('with undefined', () => {
  expect(pluck(undefined)([{ a: 1 }, { a: 2 }, { b: 1 }])).toEqual([])
})
<summary>TypeScript test</summary>
import { pipe, pluck } from 'rambda';

it("R.pluck", () => {
  const input = [
    { a: 1, b: "foo" },
    { a: 2, b: "bar" },
  ];
  const result = pipe(input, pluck("b"));
  result; // $ExpectType string[]
});

it("R.pluck without R.pipe", () => {
  interface Content {
    text: string;
  }
  const content: Content[] = [
    {
      text: "foo",
    },
  ];
  const sentences = pluck("text")(content);
  sentences; // $ExpectType string[]
});

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

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

<summary>All TypeScript definitions</summary>
prepend<T>(xToPrepend: T, iterable: T[]): T[];
prepend<T>(xToPrepend: T): (iterable: T[]) => T[];
<summary>R.prepend source</summary>
export function prepend(x) {
  return list => [x].concat(list)
}
<summary>Tests</summary>
import { prepend } from './prepend.js'

test('happy', () => {
  expect(prepend('yes')(['foo', 'bar', 'baz'])).toEqual(['yes', 'foo', 'bar', 'baz'])
})

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

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

prop


prop<K extends PropertyKey>(prop: K): <U extends { [P in K]?: unknown }>(obj: U) => U[K]

It returns the value of property propToFind in obj.

If there is no such property, it returns undefined.

const result = [
  R.prop('x')({x: 100}),
  R.prop('x')({a: 1})
]
// => [100, undefined]

Try this R.prop example in Rambda REPL

<summary>All TypeScript definitions</summary>
prop<K extends PropertyKey>(prop: K): <U extends { [P in K]?: unknown }>(obj: U) => U[K];
prop<K extends keyof U, U>(prop: K, obj: U): U[K];
<summary>R.prop source</summary>
export function prop(searchProperty) {
  return obj => (obj ? obj[searchProperty] : undefined)
}
<summary>TypeScript test</summary>
import { map, pipe, prop } from 'rambda'

describe('R.prop', () => {
  it('happy', () => {
    const result = pipe({ a: 1 }, prop('a'))

    result // $ExpectType number
  })
  it('alike R.pluck', () => {
    const result = pipe([{ a: 1 }, { a: 2 }], map(prop('a')))

    result // $ExpectType number[]
  })
})

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

propEq


propEq<T>(val: T): {
  <K extends PropertyKey>(name: K): (obj: Record<K, T>) => boolean

It returns true if obj has property propToFind and its value is equal to valueToMatch.

const obj = { foo: 'bar' }
const secondObj = { foo: 1 }

const propToFind = 'foo'
const valueToMatch = 'bar'

const result = [
  R.propEq(propToFind, valueToMatch)(obj),
  R.propEq(propToFind, valueToMatch)(secondObj)
]
// => [true, false]

Try this R.propEq example in Rambda REPL

<summary>All TypeScript definitions</summary>
propEq<T>(val: T): {
  <K extends PropertyKey>(name: K): (obj: Record<K, T>) => boolean;
  <K extends PropertyKey>(name: K, obj: Record<K, T>): boolean;
};
propEq<T, K extends PropertyKey>(val: T, name: K): (obj: Record<K, T>) => boolean;
...
...
<summary>R.propEq source</summary>
import { equalsFn } from './equals.js'

export function propEq(valueToMatch, propToFind) {
  return obj => {
    if (!obj) {
      return false
    }

    return equalsFn(valueToMatch, obj[propToFind])
  }
}
<summary>Tests</summary>
import { propEq } from './propEq.js'

const FOO = 'foo'
const BAR = 'bar'

test('happy', () => {
  const obj = { [FOO]: BAR }
  expect(propEq(BAR, FOO)(obj)).toBeTruthy()
  expect(propEq(1, FOO)(obj)).toBeFalsy()
  expect(propEq(1, 1)(null)).toBeFalsy()
})

test('returns false if called with a null or undefined object', () => {
  expect(propEq('name', 'Abby')(null)).toBeFalsy()
  expect(propEq('name', 'Abby')(undefined)).toBeFalsy()
})

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

propOr


propOr<T, P extends string>(property: P, defaultValue: T): (obj: Partial<Record<P, T>>) => T

It returns either defaultValue or the value of property in obj.

const obj = {a: 1}
const defaultValue = 'DEFAULT_VALUE'
const property = 'a'

const result = [
  R.propOr(defaultValue, property)(obj),
  R.propOr(defaultValue, 'foo')(obj)
]
// => [1, 'DEFAULT_VALUE']

Try this R.propOr example in Rambda REPL

<summary>All TypeScript definitions</summary>
propOr<T, P extends string>(property: P, defaultValue: T): (obj: Partial<Record<P, T>>) => T;
<summary>R.propOr source</summary>
import { defaultTo } from './defaultTo.js'

export function propOr(property, defaultValue) {
  return obj => {
    if (!obj) {
      return defaultValue
    }

    return defaultTo(defaultValue)(obj[property])
  }
}
<summary>Tests</summary>
import { propOr } from './propOr.js'

test('propOr', () => {
  const obj = { a: 1 }
  expect(propOr('a', 'default', )(obj)).toBe(1)
  expect(propOr('notExist', 'default')(obj)).toBe('default')
  expect(propOr('notExist', 'default')(null)).toBe('default')
})
<summary>TypeScript test</summary>
import { propOr } from 'rambda'

const obj = { foo: 'bar' }
const property = 'foo'
const fallback = 'fallback'

it('R.propOr', () => {
	const result = propOr(property, fallback)(obj)
	result // $ExpectType string
})

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

propSatisfies


propSatisfies<T>(predicate: (x: T) => boolean, property: string): (obj: Record<PropertyKey, T>) => boolean

It returns true if the object property satisfies a given predicate.

const obj = {a: {b:1}}
const property = 'a'
const predicate = x => x?.b === 1

const result = R.propSatisfies(predicate, property, obj)
// => true

Try this R.propSatisfies example in Rambda REPL

<summary>All TypeScript definitions</summary>
propSatisfies<T>(predicate: (x: T) => boolean, property: string): (obj: Record<PropertyKey, T>) => boolean;
<summary>R.propSatisfies source</summary>
export function propSatisfies(predicate, property) {
  return obj => predicate(obj[property])
}
<summary>Tests</summary>
import { propSatisfies } from './propSatisfies.js'

const obj = { a: 1 }

test('when true', () => {
  expect(propSatisfies(x => x > 0, 'a')(obj)).toBeTruthy()
})

test('when false', () => {
  expect(propSatisfies(x => x < 0, 'a')(obj)).toBeFalsy()
})
<summary>TypeScript test</summary>
import { pipe, propSatisfies } from 'rambda'

const obj = { a: 1 }

describe('R.propSatisfies', () => {
  it('happy', () => {
    const result = pipe(
      obj,
      propSatisfies(x => {
        x // $ExpectType number
        return x > 0
      }, 'a'),
    )

    result // $ExpectType boolean
  })
})

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

random


random(minInclusive: number, maxInclusive: number): number

It returns a random number between min inclusive and max inclusive.

<summary>All TypeScript definitions</summary>
random(minInclusive: number, maxInclusive: number): number;
<summary>R.random source</summary>
export function random(min, max){
  return Math.floor(Math.random() * (max - min + 1)) + min
}
<summary>Tests</summary>
import { random } from './random.js'
import { range } from './range.js'
import { uniq } from './uniq.js'

test('happy', () => {
	const result = uniq(range(100).map(() => random(0, 3))).sort()
  expect(result).toEqual([0,1,2,3])
})

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

range


range(endInclusive: number) : number[]

It returns list of numbers between startInclusive to endInclusive markers.

[R.range(5), R.range(1, 5)]
// => [[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]

Try this R.range example in Rambda REPL

<summary>All TypeScript definitions</summary>
range(endInclusive: number) : number[];
range(startInclusive: number, endInclusive: number) : number[];
<summary>R.range source</summary>
export function range(a, b) {
  const start = b === undefined ? 0 : a
  const end = b === undefined ? a : b
  if (end<=  start) {
		return []
  }
  const len = end - start
	return Array.from({ length: len + 1 }, (_, i) => start + i)
}
<summary>Tests</summary>
import { range } from './range.js'

test('happy', () => {
  expect(range(5)).toEqual([0, 1, 2, 3, 4, 5])
  expect(range(3,5)).toEqual([3, 4, 5])
  expect(range(5,3)).toEqual([])
	expect(range(0)).toEqual([])
})
<summary>TypeScript test</summary>
import { range } from 'rambda'

describe('R.range', () => {
  it('curried', () => {
    const result = [range(1, 4), range(1)]

    result // $ExpectType number[][]
  })
})

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

rangeDescending


rangeDescending(startInclusive: number, endInclusive: number) : number[]

It returns list of numbers between endInclusive to startInclusive markers.

<summary>All TypeScript definitions</summary>
rangeDescending(startInclusive: number, endInclusive: number) : number[];
rangeDescending(endInclusive: number) : number[];
<summary>R.rangeDescending source</summary>
export function rangeDescending(start, b) {
	const end = b === undefined ? 0 : b
	if (start <= end) {
		return []
	}
  const len = start - end
 	return Array.from({ length: len + 1 }, (_, i) => start - i)
}
<summary>Tests</summary>
import { rangeDescending } from './rangeDescending.js'

test('happy', () => {
  expect(rangeDescending(5)).toEqual([5, 4, 3, 2, 1, 0])
	expect(rangeDescending(7,3)).toEqual([7, 6, 5, 4,3])
	expect(rangeDescending(5, 7)).toEqual([])
	expect(rangeDescending(5, 5)).toEqual([])
})

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

reduce


reduce<T, TResult>(reducer: (prev: TResult, current: T, i: number) => TResult, initialValue: TResult): (list: T[]) => TResult

:boom: It passes index of the list as third argument to reducer function.

const list = [1, 2, 3]
const initialValue = 10
const reducer = (prev, current) => prev * current

const result = R.reduce(reducer, initialValue, list)
// => 60

Try this R.reduce example in Rambda REPL

<summary>All TypeScript definitions</summary>
reduce<T, TResult>(reducer: (prev: TResult, current: T, i: number) => TResult, initialValue: TResult): (list: T[]) => TResult;
<summary>R.reduce source</summary>
import { isArray } from './_internals/isArray.js'

export function reduce(reducer, acc) {
  return list => {
    if (list == null) {
      return acc
    }
    if (!isArray(list)) {
      throw new TypeError('reduce: list must be array or iterable')
    }
    let index = 0
    const len = list.length

    while (index < len) {
      acc = reducer(acc, list[index], index, list)
      index++
    }

    return acc
  }
}
<summary>Tests</summary>
import { concat } from './concat.js'
import { reduce } from './reduce.js'

const reducer = (prev, current, i) => {
  expect(typeof i).toBe('number')

  return prev + current
}
const initialValue = 1
const list = [1, 2, 3]
const ERROR = 'reduce: list must be array or iterable'

test('happy', () => {
  expect(reduce(reducer, initialValue)(list)).toBe(7)
})

test('with undefined as iterable', () => {
  expect(() => reduce(reducer, 0)({})).toThrowError(ERROR)
})

test('returns the accumulator for a null list', () => {
  expect(reduce(concat, [])(null)).toEqual([])
})

test('returns the accumulator for an undefined list', () => {
  expect(reduce(concat, [])(undefined)).toEqual([])
})
<summary>TypeScript test</summary>
import { pipe, reduce } from 'rambda'

it('R.reduce', () => {
  const result = pipe(
    [1, 2, 3],
    reduce((acc, val) => acc + val, 10),
  )
  result // $ExpectType number
})

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

reject


reject<T>(
	predicate: BooleanConstructor,
): (list: readonly T[]) => ("" | null | undefined | false | 0)[]

It has the opposite effect of R.filter.

const list = [1, 2, 3, 4]
const obj = {a: 1, b: 2}
const predicate = x => x > 1

const result = [
  R.reject(predicate)(list),
  R.reject(predicate)(obj)
]
// => [[1], {a: 1}]

Try this R.reject example in Rambda REPL

<summary>All TypeScript definitions</summary>
reject<T>(
	predicate: BooleanConstructor,
): (list: readonly T[]) => ("" | null | undefined | false | 0)[];
reject<T>(
	predicate: BooleanConstructor,
): (list: T[]) => ("" | null | undefined | false | 0)[];
reject<T>(
	predicate: (value: T) => boolean,
): (list: T[]) => T[];
<summary>R.reject source</summary>
import { filter } from './filter.js'

export function reject(predicate) {
  return list => filter(x => !predicate(x))(list)
}
<summary>Tests</summary>
import { reject } from './reject.js'

test('happy', () => {
  const isEven = n => n % 2 === 0

  expect(reject(isEven)([1, 2, 3, 4])).toEqual([1, 3])
})
<summary>TypeScript test</summary>
import { reject, pipe } from 'rambda'

const list = [1, 2, 3]

describe('R.reject with array', () => {
  it('within pipe', () => {
    const result = pipe(
      list,
      reject(x => {
        x // $ExpectType number
        return x > 1
      }),
    )
    result // $ExpectType number[]
  })
	it('with index', () => {
    const result = pipe(
      list,
      reject((x: number, i: number) => {
        x // $ExpectType number
        i // $ExpectType number
        return x > 1
      }),
    )
    result // $ExpectType number[]
  })
  it('narrowing type', () => {
    interface Foo {
      a: number
    }
    interface Bar extends Foo {
      b: string
    }
    interface Baz extends Foo {
      c: string
    }

    const testList: (Foo | Bar | Baz)[] = [{ a: 1 }, { a: 2 }, { a: 3 }]
    const rejectBar = (x: Foo | Bar | Baz): x is Bar => {
      return typeof (x as Bar).b === 'string'
    }
    const result = pipe(
      testList,
      reject(rejectBar),
    )
    result // $ExpectType (Foo | Baz)[]
  })
  it('narrowing type - readonly', () => {
		interface Foo {
      a: number
    }
    interface Bar extends Foo {
      b: string
    }
    interface Baz extends Foo {
      c: string
    }

    const testList: (Foo | Bar | Baz)[] = [{ a: 1 }, { a: 2 }, { a: 3 }] as const
    const rejectBar = (x: Foo | Bar | Baz): x is Bar => {
      return typeof (x as Bar).b === 'string'
    }
    const result = pipe(
      testList,
      reject(rejectBar),
    )
    result // $ExpectType (Foo | Baz)[]
  })
  it('rejecting NonNullable', () => {
    const testList = [1, 2, null, undefined, 3]
    const result = pipe(testList, reject(Boolean))
    result // $ExpectType (null | undefined)[]
  })
  it('rejecting NonNullable - readonly', () => {
    const testList = [1, 2, null, undefined, 3] as const
    const result = pipe(testList, reject(Boolean))
    result // $ExpectType (null | undefined)[]
    // @ts-expect-error
    result.includes(1)
  })
})

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

rejectObject


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

Same as R.filterObject but it returns the object with properties that do not satisfy the predicate function.

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

Try this R.rejectObject example in Rambda REPL

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

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

    return willReturn
  }
}
<summary>Tests</summary>
import { pipe } from './pipe.js'
import { rejectObject } from './rejectObject.js'

test('happy', () => {
	let testInput = { a: 1, b: 2, c: 3 }
  const result = pipe(
		testInput,
		rejectObject((x, prop, obj) => {
			expect(prop).toBeOneOf(['a', 'b', 'c'])
			expect(obj).toBe(testInput)
			return x > 1
		})
	)
	expect(result).toEqual({ a:1 })
})
<summary>TypeScript test</summary>
import { filterObject, pipe } from 'rambda'

describe('R.filterObject', () => {
  it('require explicit type', () => {
    const result = pipe(
      { a: 1, b: 2 },
      filterObject<{ b: number }>(a => {
        a // $ExpectType number
        return a > 1
      }),
    )
    result.b // $ExpectType number
  })
})

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

replace


replace(strOrRegex: RegExp | string, replacer: RegExp | string): (str: string) => string

It replaces strOrRegex found in str with replacer.

const result = [
	R.replace('o', '|1|')('foo'),
	R.replace(/o/g, '|1|')('foo'),
]
// => ['f|1|o', 'f|1||1|']

Try this R.replace example in Rambda REPL

<summary>All TypeScript definitions</summary>
replace(strOrRegex: RegExp | string, replacer: RegExp | string): (str: string) => string;
<summary>R.replace source</summary>
export function replace(pattern, replacer) {
  return str => str.replace(pattern, replacer)
}
<summary>Tests</summary>
import { replace } from './replace.js'

test('happy', () => {
  expect(replace(/\s/g, '|')('foo bar baz')).toBe('foo|bar|baz')
  expect(replace('a', '|')('foo bar baz')).toBe('foo b|r baz')
})
<summary>TypeScript test</summary>
import { replace } from 'rambda'

const str = 'foo bar foo'
const replacer = 'bar'

describe('R.replace', () => {
  it('happy', () => {
    const result = replace(/foo/g, replacer)(str)

    result // $ExpectType string
  })
  it('with string as search pattern', () => {
    const result = replace('foo', replacer)(str)

    result // $ExpectType string
  })
})

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

replaceAll


replaceAll(patterns: (RegExp | string)[], replacer: string): (input: string) => string

Same as R.replace but it accepts array of string and regular expressions instead of a single value.

const result = [
	R.replaceAll(['o', /a/g], '|1|')('foa'),
]
// => 'f|1||1|'

Try this R.replaceAll example in Rambda REPL

<summary>All TypeScript definitions</summary>
replaceAll(patterns: (RegExp | string)[], replacer: string): (input: string) => string;
<summary>R.replaceAll source</summary>
export function replaceAll(patterns, replacer) {
  return input => {
    let text = input
    patterns.forEach(singlePattern => {
      text = text.replace(singlePattern, replacer)
    })

    return text
  }
}
<summary>Tests</summary>
import { replaceAll } from './replaceAll.js'

const replacer = '|'
const patterns = [/foo/g, 'bar']
const input = 'foo bar baz foo bar'

test('happy', () => {
  const result = replaceAll(patterns, replacer)(input)
  const expected = '| | baz | bar'

  expect(result).toEqual(expected)
})
<summary>TypeScript test</summary>
import { pipe, replaceAll } from 'rambda'

const str = 'foo bar foo'
const replacer = 'bar'
const patterns = [/foo/g, 'bar']

describe('R.replaceAll', () => {
  it('happy', () => {
    const result = pipe(str, replaceAll(patterns, replacer))

    result // $ExpectType string
  })
})

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

shuffle


shuffle<T>(list: T[]): T[]

It returns a randomized copy of array.

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

export function shuffle(listInput) {
  const list = cloneList(listInput)
  let counter = list.length
  while (counter > 0) {
    const index = Math.floor(Math.random() * counter)
    counter--
    const temp = list[counter]
    list[counter] = list[index]
    list[index] = temp
  }

  return list
}
<summary>TypeScript test</summary>
import { shuffle } from 'rambdax'

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

describe('R.shuffle', () => {
  it('happy', () => {
    const result = shuffle(list)
    result // $ExpectType number[]
  })
})

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

sort


sort<T>(sortFn: (a: T, b: T) => number): (list: T[]) => T[]

It returns copy of list sorted by sortFn function, where sortFn needs to return only -1, 0 or 1.

const list = [
  {a: 2},
  {a: 3},
  {a: 1}
]
const sortFn = (x, y) => {
  return x.a > y.a ? 1 : -1
}

const result = R.sort(sortFn, list)
const expected = [
  {a: 1},
  {a: 2},
  {a: 3}
]
// => `result` is equal to `expected`

Try this R.sort example in Rambda REPL

<summary>All TypeScript definitions</summary>
sort<T>(sortFn: (a: T, b: T) => number): (list: T[]) => T[];
<summary>R.sort source</summary>
import { cloneList } from './_internals/cloneList.js'

export function sort(sortFn) {
  return list => cloneList(list).sort(sortFn)
}
<summary>Tests</summary>
import { sort } from './sort.js'

const fn = (a, b) => (a > b ? 1 : -1)

test('sort', () => {
  expect(sort((a, b) => a - b)([2, 3, 1])).toEqual([1, 2, 3])
})

test("it doesn't mutate", () => {
  const list = ['foo', 'bar', 'baz']

  expect(sort(fn)(list)).toEqual(['bar', 'baz', 'foo'])
  expect(list).toEqual(['foo', 'bar', 'baz'])
})
<summary>TypeScript test</summary>
import { pipe, sort } from 'rambda'

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

describe('R.sort', () => {
  it('happy', () => {
    const result = sort<number>((a, b) => {
      return a > b ? 1 : -1
    })(list)
    result // $ExpectType number[]
  })
  it('within pipe', () => {
    const result = pipe(
      list,
      sort((a, b) => {
        return a > b ? 1 : -1
      }),
    )
    result // $ExpectType number[]
  })
})

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

sortBy


sortBy<T>(sortFn: (x: T) => Ord): (list: T[]) => T[]

It returns copy of list sorted by sortFn function, where sortFn function returns a value to compare, i.e. it doesn't need to return only -1, 0 or 1.

const list = [
  {a: 2},
  {a: 3},
  {a: 1}
]
const sortFn = x => x.a

const result = R.sortBy(sortFn)(list)
const expected = [
  {a: 1},
  {a: 2},
  {a: 3}
]
// => `result` is equal to `expected`

Try this R.sortBy example in Rambda REPL

<summary>All TypeScript definitions</summary>
sortBy<T>(sortFn: (x: T) => Ord): (list: T[]) => T[];
<summary>R.sortBy source</summary>
import { cloneList } from './_internals/cloneList.js'

export function sortByFn (
	sortFn,
	list,
	descending
){
	const clone = cloneList(list)

	return clone.sort((a, b) => {
		const aSortResult = sortFn(a)
		const bSortResult = sortFn(b)

		if (aSortResult === bSortResult) {
			return 0
		}
		if(
			descending
		) return aSortResult > bSortResult ? -1 : 1

		return aSortResult < bSortResult ? -1 : 1
	})
}

export function sortBy(sortFn) {
  return list => sortByFn(sortFn, list, false)
}
<summary>Tests</summary>
import { sortBy } from './sortBy.js'

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

test('happy', () => {
  const expected = [{ a: 1 }, { a: 1 }, { a: 2 }, { a: 3 }]

  const result = sortBy(x => x.a)(input)
  expect(result).toEqual(expected)
})

test('with non-existing path', () => {
	expect(sortBy(x => x.b)(input)).toEqual(input)
})
<summary>TypeScript test</summary>
import { pipe, sortBy } from 'rambda'

describe('R.sortBy', () => {
  it('passing type to sort function and list', () => {
    const result = pipe(
      [{ a: 2 }, { a: 1 }, { a: 0 }],
      sortBy(x => {
        return x.a
      }),
    )

    result[0].a // $ExpectType number
  })
})

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

sortByDescending


sortByDescending<T>(sortFn: (x: T) => Ord): (list: T[]) => T[]
const list = [
  {a: 2},
  {a: 3},
  {a: 1}
]
const sortFn = x => x.a

const result = R.sortByDescending(sortFn)(list)
const expected = [
  {a: 3},
  {a: 2},
  {a: 1}
]
// => `result` is equal to `expected`

Try this R.sortByDescending example in Rambda REPL

<summary>All TypeScript definitions</summary>
sortByDescending<T>(sortFn: (x: T) => Ord): (list: T[]) => T[];
<summary>R.sortByDescending source</summary>
import { sortByFn } from './sortBy.js';

export function sortByDescending(sortFn) {
  return list => sortByFn(sortFn, list, true)
}
<summary>Tests</summary>
import { sortByDescending } from './sortByDescending.js'
import { path } from './path.js'

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

test('happy', () => {
  expect(sortByDescending(path('a.b'))(list)).toEqual(sorted)
})

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

sortByPath


sortByPath<S, K0 extends string & keyof S>(
  path: [K0]
): (list: S[]) => S[]

It sorts list by the value of path property.

const list = [
	{a: {b: 2}, id:1},
	{a: {b: 1}, id:2},
	{a: {b: 3}, id:3},
]
const result = R.sortByPath('a.b')(list)
const expected = [
	{a: {b: 1}, id:2},
	{a: {b: 2}, id:1},
	{a: {b: 3}, id:3}
]
// => `result` is equal to `expected`

Try this R.sortByPath example in Rambda REPL

<summary>All TypeScript definitions</summary>
sortByPath<S, K0 extends string & keyof S>(
  path: [K0]
): (list: S[]) => S[];
sortByPath<S, K0 extends string & keyof S>(
  path: `${K0}`
): (list: S[]) => S[];
sortByPath<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  path: [K0, K1]
): (list: S[]) => S[];
sortByPath<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  path: `${K0}.${K1}`
): (list: S[]) => S[];
...
...
<summary>R.sortByPath source</summary>
import { path } from './path.js'
import { sortBy } from './sortBy.js'

export function sortByPath(sortPath) {
  return list => sortBy(path(sortPath))(list)
}
<summary>Tests</summary>
import { sortByPath } from './sortByPath.js'

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

test('with string as path', () => {
  expect(sortByPath('a.b')(list)).toEqual(sorted)
})

test('with list of strings as path', () => {
  expect(sortByPath(['a', 'b'])(list)).toEqual(sorted)
})

test('when path is not found in any item', () => {
	const list = [{ a: { b: 3 } }, { a: { b: 1 } }, { a: {} }]
	expect(sortByPath('a.b.c.d')(list)).toEqual(list)
})
<summary>TypeScript test</summary>
import { pipe, sortByPath } from 'rambda'

const input= [{ a: { b: 2 } }, { a: { b: 1 } }]

describe('R.sortByPath', () => {
  it('with string as path', () => {
    const result = pipe(input, sortByPath('a.b'))
		result[0].a.b // $ExpectType number
  })
  it('with list of strings as path', () => {
    const result = pipe(input, sortByPath(['a', 'b']))
		result[0].a.b // $ExpectType number
  })
	it('with non-existent path', () => {
		// @ts-expect-error
		pipe(input, sortByPath(['a', 'c']))
		// @ts-expect-error
		pipe(input, sortByPath('a.c'))
	})
})

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

sortByPathDescending


sortByPathDescending<S, K0 extends string & keyof S>(
  path: [K0]
): (list: S[]) => S[]
const list = [
	{a: {b: 2}, id:1},
	{a: {b: 1}, id:2},
	{a: {b: 3}, id:3},
]
const result = R.sortByPathDescending('a.b')(list)
const expected = [
	{a: {b: 3}, id:3}
	{a: {b: 2}, id:1},
	{a: {b: 1}, id:2},
]
// => `result` is equal to `expected`

Try this R.sortByPathDescending example in Rambda REPL

<summary>All TypeScript definitions</summary>
sortByPathDescending<S, K0 extends string & keyof S>(
  path: [K0]
): (list: S[]) => S[];
sortByPathDescending<S, K0 extends string & keyof S>(
  path: `${K0}`
): (list: S[]) => S[];
sortByPathDescending<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  path: [K0, K1]
): (list: S[]) => S[];
sortByPathDescending<S, K0 extends string & keyof S, K1 extends string & keyof S[K0]>(
  path: `${K0}.${K1}`
): (list: S[]) => S[];
...
...
<summary>R.sortByPathDescending source</summary>
import { path } from './path.js'
import { sortByDescending } from './sortByDescending.js'

export function sortByPathDescending(sortPath) {
  return list => sortByDescending(path(sortPath))(list)
}
<summary>Tests</summary>
import { sortByPathDescending } from './sortByPathDescending.js'

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

test('with string as path', () => {
  expect(sortByPathDescending('a.b')(list)).toEqual(sorted)
})

test('with list of strings as path', () => {
  expect(sortByPathDescending(['a', 'b'])(list)).toEqual(sorted)
})

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

sortObject


sortObject<T, K extends string & keyof T>(predicate: (aProp: string, bProp: string, aValue: T[K], bValue: T[K]) => number): (obj: T) => T

It returns a sorted version of input object.

const predicate = (propA, propB, valueA, valueB) => valueA > valueB ? -1 : 1

const result = R.sortObject(predicate)({a:1, b: 4, c: 2})
// => {b: 4, c: 2, a: 1}

Try this R.sortObject example in Rambda REPL

<summary>All TypeScript definitions</summary>
sortObject<T, K extends string & keyof T>(predicate: (aProp: string, bProp: string, aValue: T[K], bValue: T[K]) => number): (obj: T) => T;
sortObject<T>(predicate: (aProp: string, bProp: string) => number): (obj: T) => T;
<summary>R.sortObject source</summary>
import { sort } from './sort.js'

export function sortObject(predicate) {
  return obj => {
    const keys = Object.keys(obj)
    const sortedKeys = sort((a, b) => predicate(a, b, obj[a], obj[b]))(keys)

    const toReturn = {}
    sortedKeys.forEach(singleKey => {
      toReturn[singleKey] = obj[singleKey]
    })

    return toReturn
  }
}
<summary>Tests</summary>
import { sortObject } from './sortObject.js'

const obj = {
  c: 7,
  a: 100,
  b: 1,
  d: 4,
}

test('happy', () => {
  const predicate = (a, b, aValue, bValue) => {
    if (a === 'a') {
      return -1
    }
    if (b === 'a') {
      return 1
    }
    return aValue > bValue ? -1 : 1
  }
  const result = sortObject(predicate)(obj)
  const expected = {
    a: 100,
    c: 7,
    d: 4,
    b: 1,
  }
  expect(result).toEqual(expected)
})
<summary>TypeScript test</summary>
import { sortObject, pipe } from 'rambda'

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

describe('R.sortObject', () => {
  it('predicate with all arguments', () => {
    const result = pipe(
      obj,
      sortObject((propA, propB, valueA, valueB) => {
        propA // $ExpectType string
        propB // $ExpectType string
        valueA // $ExpectType number
        valueB // $ExpectType number
        return propA > propB ? -1 : 1
      }),
    )

    result // $ExpectType { c: number; a: number; b: number; }
  })

  it('predicate with only property arguments', () => {
    const result = pipe(
      obj,
      sortObject((propA, propB) => {
        propA // $ExpectType string
        propB // $ExpectType string
        return propA > propB ? -1 : 1
      }),
    )
    result // $ExpectType { c: number; a: number; b: number; }
  })
})

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

sortWith


sortWith<T>(fns: Array<(a: T, b: T) => number>): (list: T[]) => T[]
const result = R.sortWith([
    (a, b) => a.a === b.a ? 0 : a.a > b.a ? 1 : -1,
    (a, b) => a.b === b.b ? 0 : a.b > b.b ? 1 : -1,
])([
  {a: 1, b: 2},
  {a: 2, b: 1},
  {a: 2, b: 2},
  {a: 1, b: 1},
])
const expected = [
  {a: 1, b: 1},
  {a: 1, b: 2},
  {a: 2, b: 1},
  {a: 2, b: 2},
]
// => `result` is equal to `expected`

Try this R.sortWith example in Rambda REPL

<summary>All TypeScript definitions</summary>
sortWith<T>(fns: Array<(a: T, b: T) => number>): (list: T[]) => T[];
<summary>R.sortWith source</summary>
function sortHelper(a, b, listOfSortingFns) {
  let result = 0
  let i = 0
  while (result === 0 && i < listOfSortingFns.length) {
    result = listOfSortingFns[i](a, b)
    i += 1
  }

  return result
}

export function sortWith(listOfSortingFns) {
  return list => {
    if (Array.isArray(list) === false) {
      return []
    }

    const clone = list.slice()
    clone.sort((a, b) => sortHelper(a, b, listOfSortingFns))

    return clone
  }
}
<summary>Tests</summary>
import { ascend } from './ascend.js'
import { prop } from './prop.js'
import { sortWith } from './sortWith.js'

const albums = [
  {
    artist: 'Rush',
    genre: 'Rock',
    score: 3,
    title: 'A Farewell to Kings',
  },
  {
    artist: 'Dave Brubeck Quartet',
    genre: 'Jazz',
    score: 3,
    title: 'Timeout',
  },
  {
    artist: 'Rush',
    genre: 'Rock',
    score: 5,
    title: 'Fly By Night',
  },
  {
    artist: 'Daniel Barenboim',
    genre: 'Baroque',
    score: 3,
    title: 'Goldberg Variations',
  },
  {
    artist: 'Glenn Gould',
    genre: 'Baroque',
    score: 3,
    title: 'Art of the Fugue',
  },
  {
    artist: 'Leonard Bernstein',
    genre: 'Romantic',
    score: 4,
    title: 'New World Symphony',
  },
  {
    artist: 'Don Byron',
    genre: 'Jazz',
    score: 5,
    title: 'Romance with the Unseen',
  },
  {
    artist: 'Iron Maiden',
    genre: 'Metal',
    score: 2,
    title: 'Somewhere In Time',
  },
  {
    artist: 'Danny Holt',
    genre: 'Modern',
    score: 1,
    title: 'In Times of Desparation',
  },
  {
    artist: 'Various',
    genre: 'Broadway',
    score: 3,
    title: 'Evita',
  },
  {
    artist: 'Nick Drake',
    genre: 'Folk',
    score: 1,
    title: 'Five Leaves Left',
  },
  {
    artist: 'John Eliot Gardiner',
    genre: 'Classical',
    score: 4,
    title: 'The Magic Flute',
  },
]

test('sorts by a simple property of the objects', () => {
  const sortedAlbums = sortWith([ascend(prop('title'))])(albums)
  expect(sortedAlbums).toHaveLength(albums.length)
  expect(sortedAlbums[0].title).toBe('A Farewell to Kings')
  expect(sortedAlbums[11].title).toBe('Timeout')
})

test('sorts by multiple properties of the objects', () => {
  const sortedAlbums = sortWith([ascend(prop('score')), ascend(prop('title'))])(
    albums,
  )
  expect(sortedAlbums).toHaveLength(albums.length)
  expect(sortedAlbums[0].title).toBe('Five Leaves Left')
  expect(sortedAlbums[1].title).toBe('In Times of Desparation')
  expect(sortedAlbums[11].title).toBe('Romance with the Unseen')
})

test('sorts by 3 properties of the objects', () => {
  const sortedAlbums = sortWith([
    ascend(prop('genre')),
    ascend(prop('score')),
    ascend(prop('title')),
  ])(albums)
  expect(sortedAlbums).toHaveLength(albums.length)
  expect(sortedAlbums[0].title).toBe('Art of the Fugue')
  expect(sortedAlbums[1].title).toBe('Goldberg Variations')
  expect(sortedAlbums[11].title).toBe('New World Symphony')
})

test('sorts by multiple properties using ascend and descend', () => {
  const sortedAlbums = sortWith([ascend(prop('score')), ascend(prop('title'))])(
    albums,
  )
  expect(sortedAlbums).toHaveLength(albums.length)
  expect(sortedAlbums[0].title).toBe('Five Leaves Left')
  expect(sortedAlbums[1].title).toBe('In Times of Desparation')
  expect(sortedAlbums[11].title).toBe('Romance with the Unseen')
})

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

split


split(separator: string | RegExp): (str: string) => string[]
<summary>All TypeScript definitions</summary>
split(separator: string | RegExp): (str: string) => string[];
<summary>R.split source</summary>
export function split(separator) {
  return str => str.split(separator)
}

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

splitEvery


splitEvery<T>(sliceLength: number): (input: T[]) => (T[])[]

It splits input into slices of sliceLength.

const result = [
  R.splitEvery(2)([1, 2, 3]),
  R.splitEvery(3)('foobar')
]

const expected = [
  [[1, 2], [3]],
  ['foo', 'bar']
]
// => `result` is equal to `expected`

Try this R.splitEvery example in Rambda REPL

<summary>All TypeScript definitions</summary>
splitEvery<T>(sliceLength: number): (input: T[]) => (T[])[];
<summary>R.splitEvery source</summary>
export function splitEvery(sliceLength) {
  return list => {
    if (sliceLength < 1) {
      throw new Error('First argument to splitEvery must be a positive integer')
    }

    const willReturn = []
    let counter = 0

    while (counter < list.length) {
      willReturn.push(list.slice(counter, (counter += sliceLength)))
    }

    return willReturn
  }
}
<summary>Tests</summary>
import { splitEvery } from './splitEvery.js'

test('happy', () => {
  expect(splitEvery(3)([1, 2, 3, 4, 5, 6, 7])).toEqual([[1, 2, 3], [4, 5, 6], [7]])
})
<summary>TypeScript test</summary>
import { pipe, splitEvery } from 'rambda'

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

describe('R.splitEvery', () => {
  it('happy', () => {
    const result = pipe(list, splitEvery(3))
    result // $ExpectType number[][]
  })
})

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

sum


sum(list: number[]): number
const result = R.sum(
	[1,2,3]
)
// => 6

Try this R.sum example in Rambda REPL

<summary>All TypeScript definitions</summary>
sum(list: number[]): number;
<summary>R.sum source</summary>
export function sum(list){
	return list.reduce((acc, cur) => acc + cur, 0)
}
<summary>Tests</summary>
import { sum } from './sum.js'

test('happy', () => {
  expect(sum([1,2,3])).toEqual(6)
})

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

switcher


switcher<T extends unknown>(valueToMatch: T): Switchem<T>
const list = [1, 2, 3]

const result = switcher(list.length)
	.is(x => x < 2, 4)
	.is(x => x < 4, 6)
	.default(7)
// => 6

Try this R.switcher example in Rambda REPL

<summary>All TypeScript definitions</summary>
switcher<T extends unknown>(valueToMatch: T): Switchem<T>;
switcher<T extends unknown, U extends unknown>(valueToMatch: T): Switchem2<T, U>;

// API_MARKER_END
// ============================================
<summary>R.switcher source</summary>
import { equals } from './equals.js'

const NO_MATCH_FOUND = Symbol ? Symbol('NO_MATCH_FOUND') : undefined

const getMatchingKeyValuePair = (
  cases, testValue, defaultValue
) => {
  let iterationValue

  for (let index = 0; index < cases.length; index++){
    iterationValue = cases[ index ].test(testValue)

    if (iterationValue !== NO_MATCH_FOUND){
      return iterationValue
    }
  }

  return defaultValue
}

const isEqual = (testValue, matchValue) => {
  const willReturn =
    typeof testValue === 'function' ?
      testValue(matchValue) :
      equals(testValue)(matchValue)

  return willReturn
}

const is = (testValue, matchResult = true) => ({
  key  : testValue,
  test : matchValue =>
    isEqual(testValue, matchValue) ? matchResult : NO_MATCH_FOUND,
})

class Switchem{
  constructor(
    defaultValue, cases, willMatch
  ){
    if (cases === undefined && willMatch === undefined){
      this.cases = []
      this.defaultValue = undefined
      this.willMatch = defaultValue
    } else {
      this.cases = cases
      this.defaultValue = defaultValue
      this.willMatch = willMatch
    }

    return this
  }

  default(defaultValue){
    const holder = new Switchem(
      defaultValue, this.cases, this.willMatch
    )

    return holder.match(this.willMatch)
  }

  is(testValue, matchResult){
    return new Switchem(
      this.defaultValue,
      [ ...this.cases, is(testValue, matchResult) ],
      this.willMatch
    )
  }

  match(matchValue){
    return getMatchingKeyValuePair(
      this.cases, matchValue, this.defaultValue
    )
  }
}

export function switcher(input){
  return new Switchem(input)
}
<summary>Tests</summary>
import { switcher } from './switcher.js'
import { tap } from './tap.js'

test('with undefined', () => {
  const result = switcher(undefined)
    .is(x => x === 0, '0')
    .is(x => x === undefined, 'UNDEFINED')
    .default('3')

  expect(result).toBe('UNDEFINED')
})

test('happy', () => {
  const a = true
  const b = false
  const result = switcher([ a, b ])
    .is([ false, false ], '0')
    .is([ false, true ], '1')
    .is([ true, true ], '2')
    .default('3')

  expect(result).toBe('3')
})

test('can compare objects', () => {
  const result = switcher({ a : 1 })
    .is({ a : 1 }, 'it is object')
    .is('baz', 'it is baz')
    .default('it is default')

  expect(result).toBe('it is object')
})

test('options are mixture of functions and values - input match function', () => {
  const fn = switcher('foo').is('bar', 1)
    .is('foo', x => x + 1)
    .default(1000)

  expect(fn(2)).toBe(3)
})

test('options are mixture of functions and values - input match value', () => {
  const result = switcher('bar').is('bar', 1)
    .is('foo', x => x + 1)
    .default(1000)

  expect(result).toBe(1)
})

test('return function if all options are functions', () => {
  const fn = switcher('foo')
		.is('bar', tap)
    .is('foo', x => x + 1)
    .default(9)

  expect(fn(2)).toBe(3)
})

const switchFn = input =>
  switcher(input)
    .is(x => x.length && x.length === 7, 'has length of 7')
    .is('baz', 'it is baz')
    .default('it is default')

test('works with function as condition', () => {
  expect(switchFn([ 0, 1, 2, 3, 4, 5, 6 ])).toBe('has length of 7')
})

test('works with string as condition', () => {
  expect(switchFn('baz')).toBe('it is baz')
})

test('fallback to default input when no matches', () => {
  expect(switchFn(1)).toBe('it is default')
})
<summary>TypeScript test</summary>
import { switcher } from 'rambda'

describe('R.switcher', () => {
  it('no transformation', () => {
    const list = [1, 2, 3]

    const result = switcher(list.length)
      .is(x => x < 2, 4)
      .is(x => x < 4, 6)
      .default(7)

    result // $ExpectType number
  })
  it('with transformation', () => {
    const list = [1, 2, 3]
    type Stage = 'firstStage' | 'secondStage' | 'thirdStage'

    const result = switcher<number, Stage>(list.length)
      .is(x => x < 2, 'firstStage')
      .is(x => x < 4, 'secondStage')
      .default('thirdStage')

    result // $ExpectType Stage
  })
})

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

symmetricDifference


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

It returns all items that are in either of the lists, but not in both.

R.equals is used to determine equality.

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

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

Try this R.symmetricDifference example in Rambda REPL

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

export function symmetricDifference(listA) {
	return listB => [
		...filter(excludes(listB))(listA),
		...filter(excludes(listA))(listB),
	]
}
<summary>Tests</summary>
import { symmetricDifference } from './symmetricDifference.js'

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

test('symmetricDifference with objects', () => {
  const list1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
  const list2 = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]
  expect(symmetricDifference(list1)(list2)).toEqual([
    { id: 1 },
    { id: 2 },
    { id: 5 },
    { id: 6 },
  ])
})
<summary>TypeScript test</summary>
import { symmetricDifference } from 'rambda'

describe('R.symmetricDifference', () => {
  it('happy', () => {
    const list1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
    const list2 = [{ id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]
    const result = symmetricDifference(list1)(list2)

    result // $ExpectType { id: number; }[]
  })
})

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

tail


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

It returns all but the first element of input.

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

Try this R.tail example in Rambda REPL

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

export function tail(listOrString) {
  return drop(1)(listOrString)
}
<summary>Tests</summary>
import { tail } from './tail.js'

test('tail', () => {
  expect(tail([1, 2, 3])).toEqual([2, 3])
  expect(tail([1, 2])).toEqual([2])
  expect(tail([1])).toEqual([])
  expect(tail([])).toEqual([])

  expect(tail('abc')).toBe('bc')
  expect(tail('ab')).toBe('b')
  expect(tail('a')).toBe('')
  expect(tail('')).toBe('')
})
<summary>TypeScript test</summary>
import { map, pipe, tail } from 'rambda'

describe('R.tail', () => {
  it('with string', () => {
    const result = tail('foo')

    result // $ExpectType string
  })
  it('with list - using const on short array', () => {
    const result = pipe(
      [1] as const,
      map(x => x * 2),
      tail,
    )
    result // $ExpectType []
  })
  it('with list - using const on empty array', () => {
    const result = pipe(
      [] as const,
      map(x => x * 2),
      tail,
    )
    result // $ExpectType []
  })
  it('with list - using const', () => {
    const result = pipe(
      [1, 2, 3] as const,
      map(x => x * 2),
      tail,
    )
    result // $ExpectType [number, number]
  })
  it('with list - mixed types', () => {
    const result = tail(['foo', 'bar', 1, 2, 3])

    result // $ExpectType (string | number)[]
  })
})

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

take


take<T>(howMany: number): {
  (input: string): string

It returns the first howMany elements of input.

const howMany = 2

const result = [
  R.take(howMany)([1, 2, 3]),
  R.take(howMany, 'foobar'),
]
// => [[1, 2], 'fo']

Try this R.take example in Rambda REPL

<summary>All TypeScript definitions</summary>
take<T>(howMany: number): {
  (input: string): string;
  (input: readonly T[]): T[];
  (input: T[]): T[];
};
...
...
<summary>R.take source</summary>
import { baseSlice } from './_internals/baseSlice.js'

export function take(numberOfItems) {
  return input => {
    if (numberOfItems < 0) {
      return input.slice()
    }
    if (typeof input === 'string') {
      return input.slice(0, numberOfItems)
    }

    return baseSlice(input, 0, numberOfItems)
  }
}
<summary>Tests</summary>
import { take } from './take.js'

test('happy', () => {
  const arr = ['foo', 'bar', 'baz']

  expect(take(1)(arr)).toEqual(['foo'])
  expect(arr).toEqual(['foo', 'bar', 'baz'])
  expect(take(2)(['foo', 'bar', 'baz'])).toEqual(['foo', 'bar'])
  expect(take(3)(['foo', 'bar', 'baz'])).toEqual(['foo', 'bar', 'baz'])
  expect(take(4)(['foo', 'bar', 'baz'])).toEqual(['foo', 'bar', 'baz'])
  expect(take(3)('rambda')).toBe('ram')
})

test('with negative index', () => {
  expect(take(-1)([1, 2, 3])).toEqual([1, 2, 3])
  expect(take(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3])
})

test('with zero index', () => {
  expect(take(0)([1, 2, 3])).toEqual([])
})

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

takeLast


takeLast<T>(howMany: number): {
  (input: string): string

It returns the last howMany elements of input.

const howMany = 2

const result = [
  R.takeLast(howMany)([1, 2, 3]),
  R.takeLast(howMany)('foobar'),
]
// => [[2, 3], 'ar']

Try this R.takeLast example in Rambda REPL

<summary>All TypeScript definitions</summary>
takeLast<T>(howMany: number): {
  (input: string): string;
  (input: readonly T[]): T[];
  (input: T[]): T[];
};
...
...
<summary>R.takeLast source</summary>
import { baseSlice } from './_internals/baseSlice.js'

export function takeLast(numberOfItems) {
  return input => {
    const len = input.length
    if (numberOfItems < 0) {
      return input.slice()
    }
    let numValue = numberOfItems > len ? len : numberOfItems

    if (typeof input === 'string') {
      return input.slice(len - numValue)
    }

    numValue = len - numValue

    return baseSlice(input, numValue, len)
  }
}
<summary>Tests</summary>
import { takeLast } from './takeLast.js'

test('with arrays', () => {
  expect(takeLast(1)(['foo', 'bar', 'baz'])).toEqual(['baz'])
  expect(takeLast(2)(['foo', 'bar', 'baz'])).toEqual(['bar', 'baz'])
  expect(takeLast(3)(['foo', 'bar', 'baz'])).toEqual(['foo', 'bar', 'baz'])
  expect(takeLast(4)(['foo', 'bar', 'baz'])).toEqual(['foo', 'bar', 'baz'])
  expect(takeLast(10)(['foo', 'bar', 'baz'])).toEqual(['foo', 'bar', 'baz'])
})

test('with strings', () => {
  expect(takeLast(3)('rambda')).toBe('bda')
  expect(takeLast(7)('rambda')).toBe('rambda')
})

test('with negative index', () => {
  expect(takeLast(-1)([1, 2, 3])).toEqual([1, 2, 3])
  expect(takeLast(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3])
})

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

takeLastWhile


takeLastWhile<T>(predicate: (x: T) => boolean): (input: T[]) => T[]
const result = R.takeLastWhile(x => x > 2)([1, 2, 3, 4])
// => [3, 4]

Try this R.takeLastWhile example in Rambda REPL

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

    const toReturn = []
    let counter = input.length

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

    return toReturn.reverse()
  }
}
<summary>Tests</summary>
import { takeLastWhile } from './takeLastWhile.js'

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

test('happy', () => {
  const predicate = x => x > 2
  const result = takeLastWhile(predicate)(list)
  expect(result).toEqual([3, 4])
})

test('predicate is always true', () => {
  const predicate = () => true
  const result = takeLastWhile(predicate)(list)
  expect(result).toEqual(list)
})

test('predicate is always false', () => {
  const predicate = () => false
  const result = takeLastWhile(predicate)(list)
  expect(result).toEqual([])
})

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

takeWhile


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

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

Try this R.takeWhile example in Rambda REPL

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

    while (counter < iterable.length) {
      const item = iterable[counter++]
      if (!predicate(item)) {
        break
      }
      toReturn.push(item)
    }
    return toReturn
  }
}
<summary>Tests</summary>
import { takeWhile } from './takeWhile.js'

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

test('happy', () => {
  const result = takeWhile(x => x < 3)(list)
  expect(result).toEqual([1, 2])
})

test('always true', () => {
  const result = takeWhile(x => true)(list)
  expect(result).toEqual(list)
})

test('always false', () => {
  const result = takeWhile(x => 0)(list)
  expect(result).toEqual([])
})
<summary>TypeScript test</summary>
import { pipe, takeWhile } from 'rambda'

const list = [1, 2, 3]

it('R.takeWhile', () => {
  const result = pipe(
    list,
    takeWhile(x => x > 1),
    takeWhile((x, i) => i + x > 1),
  )
  result // $ExpectType number[]
})

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

tap


tap<T>(fn: (x: T) => void): (input: T) => T

It applies function fn to input x and returns x.

One use case is debugging in the middle of R.pipe chain.

const list = [1, 2, 3]

const result = R.pipe(
	list,
  R.filter(x => x > 1),
  R.tap(console.log),
  R.map(x => x * 2)
)
// => `2` and `3` will be logged

Try this R.tap example in Rambda REPL

<summary>All TypeScript definitions</summary>
tap<T>(fn: (x: T) => void): (input: T) => T;
<summary>R.tap source</summary>
export function tap(fn) {
  return x => {
    fn(x)

    return x
  }
}

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

test


test(regExpression: RegExp): (str: string) => boolean

It determines whether str matches regExpression.

R.test(/^f/)('foo')
// => true

Try this R.test example in Rambda REPL

<summary>All TypeScript definitions</summary>
test(regExpression: RegExp): (str: string) => boolean;
<summary>R.test source</summary>
export function test(pattern) {
  return str => str.search(pattern) !== -1
}
<summary>Tests</summary>
import { test as testMethod } from './test.js'

test('happy', () => {
  expect(testMethod(/^x/)('xyz')).toBeTruthy()
  expect(testMethod(/^y/)('xyz')).toBeFalsy()
})
<summary>TypeScript test</summary>
import { test } from 'rambda'

const input = 'foo   '
const regex = /foo/

it('R.test', () => {
  const result = test(regex)(input)

  result // $ExpectType boolean
})

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

transformPropObject


transformPropObject<T extends object, K extends keyof T, Value>(
  valueMapper: (value: T[K]) => Value,
  prop: K,
): (data: T) => MergeTypes<Omit<T, K> & { [P in K]: Value }>
const fn = (x) => x > 2
const obj = {a: 1, b: 2}

const result = R.transformPropObject(fn, 'a')(obj)
// => {a: false, b: 2}

Try this R.transformPropObject example in Rambda REPL

<summary>All TypeScript definitions</summary>
transformPropObject<T extends object, K extends keyof T, Value>(
  valueMapper: (value: T[K]) => Value,
  prop: K,
): (data: T) => MergeTypes<Omit<T, K> & { [P in K]: Value }>;
<summary>TypeScript test</summary>
import {  transformPropObject, pipe } from 'rambda'

it('R.transformPropObject', () => {
	const result = pipe(
		{ a: 1, b: 'foo' },
		transformPropObject(x => {
			x // $ExpectType number
			return x > 2
		}, 'a'),
	)

	result // $ExpectType { b: string; a: boolean; }
})

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

tryCatch


tryCatch<T, U>(
  fn: (input: T) => U,
  fallback: U
): (input: T) => U

It returns function that runs fn in try/catch block. If there was an error, then fallback is used to return the result.

const fn = x => x.foo

const result = [
  R.tryCatch(fn, false)(null),
  R.tryCatch(fn, false)({foo: 'bar'})
]
// => [false, 'bar']

Try this R.tryCatch example in Rambda REPL

<summary>All TypeScript definitions</summary>
tryCatch<T, U>(
  fn: (input: T) => U,
  fallback: U
): (input: T) => U;
<summary>R.tryCatch source</summary>
export function tryCatch(fn, fallback) {
  return input => {
    try {
      return fn(input)
    } catch (e) {
      return fallback
    }
  }
}
<summary>Tests</summary>
import { prop } from './prop.js'
import { tryCatch } from './tryCatch.js'

test('happy', () => {
  const fn = () => {
    throw new Error('foo')
  }
  const result = tryCatch(fn, () => true)()
  expect(result).toBeTruthy()
})

test('when fallback is used', () => {
  const fn = x => x.x

  expect(tryCatch(fn, false)(null)).toBeFalsy()
})

test('with json parse', () => {
  const good = () => JSON.parse(JSON.stringify({ a: 1 }))
  const bad = () => JSON.parse('a{a')

  expect(tryCatch(good, 1)()).toEqual({ a: 1 })
  expect(tryCatch(bad, 1)()).toBe(1)
})

test('when fn is used', () => {
  const fn = prop('x')

  expect(tryCatch(fn, false)({})).toBeUndefined()
  expect(tryCatch(fn, false)({ x: 1 })).toBe(1)
})
<summary>TypeScript test</summary>
import { map, pipe, tryCatch } from 'rambda'

describe('R.tryCatch', () => {
  it('happy', () => {
    const result = pipe(
      ['{a:1', '{"b": 2}'],
      map(
        tryCatch(x => {
          return JSON.parse(x) as string
        }, null),
      ),
    )

    result // $ExpectType (string | null)[]
  })
})

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

type


type(x: any): RambdaTypes

It accepts any input and it returns its type.

:boom: NaN, Promise and Async are types specific for Rambda.

const result = R.type(() => {}) // => 'Function'
R.type(async () => {}) // => 'Async'
R.type([]) // => 'Array'
R.type({}) // => 'Object'
R.type('foo') // => 'String'
R.type(1) // => 'Number'
R.type(true) // => 'Boolean'
R.type(null) // => 'Null'
R.type(/[A-z]/) // => 'RegExp'
R.type('foo'*1) // => 'NaN'

const delay = ms => new Promise(resolve => {
  setTimeout(function () {
    resolve()
  }, ms)
})
R.type(delay) // => 'Promise'

Try this R.type example in Rambda REPL

<summary>All TypeScript definitions</summary>
type(x: any): RambdaTypes;
<summary>R.type source</summary>
export function type(input) {
  if (input === null) {
    return 'Null'
  }
  if (input === undefined) {
    return 'Undefined'
  }
  if (Number.isNaN(input)) {
    return 'NaN'
  }
  const typeResult = Object.prototype.toString.call(input).slice(8, -1)
  return typeResult === 'AsyncFunction' ? 'Promise' : typeResult
}
<summary>Tests</summary>
import { type as typeRamda } from 'ramda'

import { type } from './type.js'

test('with buffer', () => {
  expect(type(new Buffer.from('foo'))).toBe('Uint8Array')
})

test('with array buffer', () => {
  expect(type(new ArrayBuffer(8))).toBe('ArrayBuffer')
})

test('with big int', () => {
  expect(type(BigInt(9007199254740991))).toBe('BigInt')
})

test('with generators', () => {
  function* generator() {
    yield 1
    yield 2
    yield 3
  }

  const gen = generator()
  expect(type(generator)).toBe('GeneratorFunction')
  expect(type(gen)).toBe('Generator')
})

test('with Date', () => {
  const date = new Date('December 17, 1995 03:24:00')
  expect(type(date)).toBe('Date')
})

test('with infinity', () => {
  expect(type(Number.POSITIVE_INFINITY)).toBe('Number')
})

test('with weak map', () => {
  expect(type(new WeakMap())).toBe('WeakMap')
})

test('with map', () => {
  expect(type(new Map())).toBe('Map')
})

test('with symbol', () => {
  expect(type(Symbol())).toBe('Symbol')
})

test('with simple promise', () => {
  expect(type(Promise.resolve(1))).toBe('Promise')
})

test('with new Boolean', () => {
  expect(type(new Boolean(true))).toBe('Boolean')
})

test('with new String', () => {
  expect(type(new String('I am a String object'))).toBe('String')
})

test('with new Number', () => {
  expect(type(new Number(1))).toBe('Number')
})

test('with error', () => {
  expect(type(Error('foo'))).toBe('Error')
  expect(typeRamda(Error('foo'))).toBe('Error')
})

test('with error - wrong @types/ramda test', () => {
  // @types/ramda expect the result to be 'Error' but it is not
  class ExtendedError extends Error {}
  expect(type(ExtendedError)).toBe('Function')
  expect(typeRamda(ExtendedError)).toBe('Function')
})

test('with new promise', () => {
  const delay = ms =>
    new Promise(resolve => {
      setTimeout(() => {
        resolve(ms + 110)
      }, ms)
    })

  expect(type(delay(10))).toBe('Promise')
})

test('async function', () => {
  expect(type(async () => {})).toBe('Promise')
})

test('async arrow', () => {
  const asyncArrow = async () => {}
  expect(type(asyncArrow)).toBe('Promise')
})

test('function', () => {
  const fn1 = () => {}
  const fn2 = () => {}

  function fn3() {}
  ;[() => {}, fn1, fn2, fn3].map(val => {
    expect(type(val)).toBe('Function')
  })
})

test('object', () => {
  expect(type({})).toBe('Object')
})

test('number', () => {
  expect(type(1)).toBe('Number')
})

test('boolean', () => {
  expect(type(false)).toBe('Boolean')
})

test('string', () => {
  expect(type('foo')).toBe('String')
})

test('null', () => {
  expect(type(null)).toBe('Null')
})

test('array', () => {
  expect(type([])).toBe('Array')
  expect(type([1, 2, 3])).toBe('Array')
})

test('regex', () => {
  expect(type(/\s/g)).toBe('RegExp')
})

test('undefined', () => {
  expect(type(undefined)).toBe('Undefined')
})

test('not a number', () => {
  expect(type(Number('s'))).toBe('NaN')
})

test('set', () => {
  const exampleSet = new Set([1, 2, 3])
  expect(type(exampleSet)).toBe('Set')
  expect(typeRamda(exampleSet)).toBe('Set')
})

test('function inside object 1', () => {
  const obj = {
    f() {
      return 4
    },
  }

  expect(type(obj.f)).toBe('Function')
  expect(typeRamda(obj.f)).toBe('Function')
})

test('function inside object 2', () => {
  const name = 'f'
  const obj = {
    [name]() {
      return 4
    },
  }
  expect(type(obj.f)).toBe('Function')
  expect(typeRamda(obj.f)).toBe('Function')
})
<summary>TypeScript test</summary>
import { type } from 'rambda'

describe('R.type', () => {
  it('happy', () => {
    const result = type(4)

    result // $ExpectType RambdaTypes
  })
})

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

union


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

It takes two lists and return a new list containing a merger of both list with removed duplicates.

R.equals is used to compare for duplication.

const result = R.union([1,2,3])([3,4,5]);
// => [1, 2, 3, 4, 5]

Try this R.union example in Rambda REPL

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

export function union(listA) {
  return listB => [
		...listA,
		...listB.filter(excludes(listA)),
	]
}
<summary>Tests</summary>
import { union } from './union.js'

test('happy', () => {
  expect(union([1, 2])([2, 3])).toEqual([1, 2, 3])
})

test('with list of objects', () => {
  const list1 = [{ a: 1 }, { a: 2 }]
  const list2 = [{ a: 2 }, { a: 3 }]
  const result = union(list1)(list2)
  expect(result).toEqual([{ a: 1 }, { a: 2 }, { a: 3 }])
})
<summary>TypeScript test</summary>
import { union } from 'rambda'

describe('R.union', () => {
  it('happy', () => {
    const result = union([1, 2])([2, 3])

    result // $ExpectType number[]
  })
  it('with array of objects - case 1', () => {
    const list1 = [{ a: 1 }, { a: 2 }]
    const list2 = [{ a: 2 }, { a: 3 }]
    const result = union(list1)(list2)
    result // $ExpectType { a: number; }[]
  })
  it('with array of objects - case 2', () => {
    const list1 = [{ a: 1, b: 1 }, { a: 2 }]
    const list2 = [{ a: 2 }, { a: 3, b: 3 }]
    const result = union(list1)(list2)
    result[0].a // $ExpectType number
    result[0].b // $ExpectType number | undefined
  })
})

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

unionWith


unionWith<T>(predicate: (x: T, y: T) => boolean, x: T[]): (y: T[]) => T[]
const result = R.pipe(
	[{a: 1, b: 1}, {a: 2, b: 1}],
	R.unionWith((x, y) => x === y, [{a: 2, b: 2}, {a: 3, b: 2}]),
)
// => [{a: 1, b: 1}, {a: 2, b: 1}, {a: 3, b: 2}]

Try this R.unionWith example in Rambda REPL

<summary>All TypeScript definitions</summary>
unionWith<T>(predicate: (x: T, y: T) => boolean, x: T[]): (y: T[]) => T[];
<summary>R.unionWith source</summary>
export function unionWith(predicate, x) {
  return y => {
    const filtered = y.filter(yInstance => {
			return x.every(xInstance => {
				return !predicate(xInstance, yInstance)
			})
    })

    return [...x, ...filtered]
  }
}
<summary>Tests</summary>
import { unionWith } from './unionWith.js'
import { pipe } from './pipe.js'

test('happy', () => {
	const list1 = [{a: 1, b: 1}, {a: 2, b: 1}]
	const list2 = [{a: 2, b: 2}, {a: 3, b: 2}]
	const result = pipe(
		list2,
		unionWith((x, y) => {
			return x.a === y.a
		}, list1),
	)
	expect(result).toEqual([{a: 1, b: 1}, {a: 2, b: 1}, {a: 3, b: 2}])
})
<summary>TypeScript test</summary>
import { pipe, unionWith } from 'rambda'

describe('R.unionWith', () => {
  it('happy', () => {
		const list = [{a: 1, b: 1}, {a: 2, b: 1}]
    const result = pipe(
			list,
			unionWith((x, y) => {
				x.a // $ExpectType number
				y.b // $ExpectType number
				return x.a === y.a
			}, [{a: 2, b: 2}, {a: 3, b: 2}]),
		)

    result[0].a // $ExpectType number
    result[0].b // $ExpectType number
  })
})

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

uniq


uniq<T>(list: T[]): T[]

It returns a new array containing only one copy of each element of list.

R.equals is used to determine equality.

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

R.uniq(list)
// => [1, {a: 1}, {a: 2}]

Try this R.uniq example in Rambda REPL

<summary>All TypeScript definitions</summary>
uniq<T>(list: T[]): T[];
<summary>R.uniq source</summary>
import { _Set } from './_internals/set.js'

export function uniq(list) {
  const set = new _Set()
  const willReturn = []
  list.forEach(item => {
    if (set.checkUniqueness(item)) {
      willReturn.push(item)
    }
  })

  return willReturn
}
<summary>Tests</summary>
import { uniq } from './uniq.js'

test('happy', () => {
  const list = [1, 2, 3, 3, 3, 1, 2, 0]
  expect(uniq(list)).toEqual([1, 2, 3, 0])
})

test('with object', () => {
  const list = [{ a: 1 }, { a: 2 }, { a: 1 }, { a: 2 }]
  expect(uniq(list)).toEqual([{ a: 1 }, { a: 2 }])
})

test('with nested array', () => {
  expect(uniq([[42], [42]])).toEqual([[42]])
})

test('with booleans', () => {
  expect(uniq([[false], [false], [true]])).toEqual([[false], [true]])
})

test('with falsy values', () => {
  expect(uniq([undefined, null])).toEqual([undefined, null])
})

test('can distinct between string and number', () => {
  expect(uniq([1, '1'])).toEqual([1, '1'])
})
<summary>TypeScript test</summary>
import { uniq } from 'rambda'

describe('R.uniq', () => {
  it('happy', () => {
    const result = uniq([1, 2, 3, 3, 3, 1, 2, 0])
    result // $ExpectType number[]
  })
})

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

uniqBy


uniqBy<T, U>(fn: (x: T) => U): (list: T[]) => T[]

It applies uniqueness to input list based on function that defines what to be used for comparison between elements.

R.equals is used to determine equality.

const list = [{a:1}, {a:2}, {a:1}]
const result = R.uniqBy(x => x)(list)

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

Try this R.uniqBy example in Rambda REPL

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

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

    return list.filter(item => set.checkUniqueness(fn(item)))
  }
}
<summary>Tests</summary>
import { uniqBy } from './uniqBy.js'

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

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

test('uses R.uniq', () => {
  const list = [{ a: 1 }, { a: 2 }, { a: 1 }]
  const expected = [{ a: 1 }, { a: 2 }]
  expect(uniqBy(x => x)(list)).toEqual(expected)
})
<summary>TypeScript test</summary>
import { uniqBy } from 'rambda'

describe('R.uniqBy', () => {
  it('happy', () => {
    const result = uniqBy(Math.abs)([-2, -1, 0])

    result // $ExpectType number[]
  })
})

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

uniqWith


uniqWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[]

It returns a new array containing only one copy of each element in list according to predicate function.

This predicate should return true, if two elements are equal.

const list = [
  {id: 0, title:'foo'},
  {id: 1, title:'bar'},
  {id: 2, title:'baz'},
  {id: 3, title:'foo'},
  {id: 4, title:'bar'},
]

const expected = [
  {id: 0, title:'foo'},
  {id: 1, title:'bar'},
  {id: 2, title:'baz'},
]

const predicate = (x,y) => x.title === y.title

const result = R.uniqWith(predicate)(list)
// => `result` is equal to `expected`

Try this R.uniqWith example in Rambda REPL

<summary>All TypeScript definitions</summary>
uniqWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[];
<summary>R.uniqWith source</summary>
function includesWith(predicate, target, list) {
  let willReturn = false
  let index = -1

  while (++index < list.length && !willReturn) {
    const value = list[index]

    if (predicate(target, value)) {
      willReturn = true
    }
  }

  return willReturn
}

export function uniqWith(predicate) {
  return list => {
    let index = -1
    const willReturn = []

    while (++index < list.length) {
      const value = list[index]

      if (!includesWith(predicate, value, willReturn)) {
        willReturn.push(value)
      }
    }

    return willReturn
  }
}
<summary>Tests</summary>
import { uniqWith } from './uniqWith.js'

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

test('happy', () => {
  const fn = (x, y) => x.a === y.a

  const result = uniqWith(fn)(list)
  expect(result).toEqual([{ a: 1 }])
})

test('with list of strings', () => {
  const fn = (x, y) => x.length === y.length
  const list = ['0', '11', '222', '33', '4', '55']
  const result = uniqWith(fn)(list)
  expect(result).toEqual(['0', '11', '222'])
})

test('should return items that are not equal to themselves', () => {
  // test case based on https://github.com/remeda/remeda/issues/999
  const data = [
    { id: 1, reason: 'No name' },
    { id: 1, reason: 'No name' },
    { reason: 'No name' },
    { reason: 'No name' },
  ]
  const expectedResult = [
    { id: 1, reason: 'No name' },
    { reason: 'No name' },
    { reason: 'No name' },
  ]

  const result = uniqWith((errorA, errorB) => {
    // the objects with no ids should effectively be ignored from removal of duplicates
    if (errorA.id === undefined || errorB.id === undefined) {
      return false
    }
    return errorA.id === errorB.id
  })(data)

  expect(result).toEqual(expectedResult)
})
<summary>TypeScript test</summary>
import { pipe, uniqWith } from 'rambda'

describe('R.uniqWith', () => {
  it('happy', () => {
    const result = pipe(
      [{ a: 1 }, { a: 1 }],
      uniqWith((x, y) => x.a === y.a),
    )
    result // $ExpectType { a: number; }[]
  })
})

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

unless


unless<T, U>(predicate: (x: T) => boolean, whenFalseFn: (x: T) => U): (x: T) => T | U

The method returns function that will be called with argument input.

If predicate(input) returns false, then the end result will be the outcome of whenFalse(input).

In the other case, the final output will be the input itself.

const fn = R.unless(
  x => x > 2,
  x => x + 10
)

const result = [
  fn(1),
  fn(5)
]
// => [11, 5]

Try this R.unless example in Rambda REPL

<summary>All TypeScript definitions</summary>
unless<T, U>(predicate: (x: T) => boolean, whenFalseFn: (x: T) => U): (x: T) => T | U;
unless<T>(predicate: (x: T) => boolean, whenFalseFn: (x: T) => T): (x: T) => T;
<summary>R.unless source</summary>
export function unless(predicate, whenFalseFn) {
  return input => {
    if (predicate(input)) {
      return input
    }

    return whenFalseFn(input)
  }
}
<summary>Tests</summary>
import { unless } from './unless.js'

test('happy', () => {
  expect(
    unless(
      x => x > 10,
      x => x + 1,
    )(20),
  ).toEqual(20)
  expect(
    unless(
      x => x > 10,
      x => x + 1,
    )(5),
  ).toEqual(6)
})
<summary>TypeScript test</summary>
import { pipe, unless } from 'rambda'

const inc = (x: number) => x + 1

describe('R.unless', () => {
  it('happy', () => {
    const result = pipe(
      1,
      unless(x => x > 5, inc),
    )
    result // $ExpectType number
  })
  it('with two different types', () => {
    const result = pipe(
      1,
      unless(
        x => {
          x // $ExpectType number
          return x > 5
        },
        x => {
          x // $ExpectType number
          return `${x}-foo`
        },
      ),
    )
    result // $ExpectType string | number
  })
})

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

unwind


unwind<S extends string>(prop: S): <T extends Record<S, readonly any[]>>(obj: T) => Array<MergeTypes<Omit<T, S> & { [K in S]: T[S][number] }>>

It takes an object and a property name. The method will return a list of objects, where each object is a shallow copy of the input object, but with the property array unwound.

const obj = {
  a: 1,
  b: [2, 3],
}
const result = R.unwind('b')(obj)
const expected = [{a:1, b:2}, {a:1, b:3}]
// => `result` is equal to `expected`

Try this R.unwind example in Rambda REPL

<summary>All TypeScript definitions</summary>
unwind<S extends string>(prop: S): <T extends Record<S, readonly any[]>>(obj: T) => Array<MergeTypes<Omit<T, S> & { [K in S]: T[S][number] }>>;
<summary>R.unwind source</summary>
export function unwind(property) {
  return obj => {
    return obj[property].map(x => ({
      ...obj,
      [property]: x,
    }))
  }
}
<summary>Tests</summary>
import { unwind } from './unwind.js'

test('happy', () => {
  const obj = {
    a: 1,
    b: [2, 3],
    c: [3, 4],
  }
  const expected = [
    {
      a: 1,
      b: 2,
      c: [3, 4],
    },
    {
      a: 1,
      b: 3,
      c: [3, 4],
    },
  ]
  const result = unwind('b')(obj)
  expect(result).toEqual(expected)
})
<summary>TypeScript test</summary>
import { pipe, unwind } from 'rambda'

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

describe('R.unwind', () => {
  it('happy', () => {
    const [result] = unwind('b')(obj)
    result.a // $ExpectType number
    result.b // $ExpectType number
  })
  it('inside pipe', () => {
    const [result] = pipe(obj, unwind('b'))
    result.a // $ExpectType number
    result.b // $ExpectType number
  })
})

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

update


update<T>(index: number, newValue: T): (list: T[]) => T[]

It returns a copy of list with updated element at index with newValue.

const index = 2
const newValue = 88
const list = [1, 2, 3, 4, 5]

const result = R.update(index, newValue)(list)
// => [1, 2, 88, 4, 5]

Try this R.update example in Rambda REPL

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

export function update(index, newValue) {
  return list => {
    const clone = cloneList(list)
    if (index === -1) {
      return clone.fill(newValue, index)
    }

    return clone.fill(newValue, index, index + 1)
  }
}
<summary>Tests</summary>
import { update } from './update.js'

const list = [1, 2, 3]

test('happy', () => {
  const newValue = 8
  const index = 1
  const result = update(index, newValue)(list)

  const expected = [1, 8, 3]
  expect(result).toEqual(expected)
})

test('list has no such index', () => {
  const newValue = 8
  const index = 10
  const result = update(index, newValue)(list)

  expect(result).toEqual(list)
})

test('with negative index', () => {
  expect(update(-1, 10)([1])).toEqual([10])
  expect(update(-1, 10)([])).toEqual([])
  expect(update(-1, 10)(list)).toEqual([1, 2, 10])
  expect(update(-2, 10)(list)).toEqual([1, 10, 3])
  expect(update(-3, 10)(list)).toEqual([10, 2, 3])
})

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

when


when<T, U extends T>(predicate: (x: T) => x is U, whenTrueFn: (x: U) => T): (input: T) => T

It pass input to predicate function and if the result is true, it will return the result of whenTrueFn(input). If the predicate returns false, then it will simply return input.

const predicate = x => typeof x === 'number'
const fn = R.when(predicate)(x => x + 1)

const positiveInput = 88
const negativeInput = 'foo'

const result = [
  fn(positiveInput),
  fn(negativeInput),
]

const expected = [
  89,
  'foo1',
]
// => `result` is equal to `expected`

Try this R.when example in Rambda REPL

<summary>All TypeScript definitions</summary>
when<T, U extends T>(predicate: (x: T) => x is U, whenTrueFn: (x: U) => T): (input: T) => T;
when<T>(predicate: (x: T) => boolean, whenTrueFn: (x: T) => T): (input: T) => T;
when<T, U>(predicate: (x: T) => boolean, whenTrueFn: (x: T) => U): (input: T) => T | U;
<summary>R.when source</summary>
export function when(predicate, whenTrueFn) {
  return input => {
    if (!predicate(input)) {
      return input
    }

    return whenTrueFn(input)
  }
}
<summary>Tests</summary>
import { when } from './when.js'

const predicate = x => typeof x === 'number'

test('happy', () => {
  const fn = when(predicate, x => x + 1)
  expect(fn(11)).toBe(12)
  expect(fn('foo')).toBe('foo')
})
<summary>TypeScript test</summary>
import { head, pipe, tap, when } from 'rambda'

function notNull<T>(a: T | null | undefined): a is T {
  return a != null
}

describe('R.when', () => {
  it('happy', () => {
    const result = pipe(
      1,
      when(
        x => x > 2,
        x => x,
      ),
      tap(x => {
        x // $ExpectType number
      }),
      when(
        x => x > 2,
        x => String(x),
      ),
    )

    result // $ExpectType string | number
  })

	it('with assertion of type', () => {
    const result = pipe(
      [1, null, 2, 3],
      head,
      when(notNull, x => x + 1),
    )
    result // $ExpectType number | null
  })
})

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

zip


zip<K>(x: K[]): <V>(y: V[]) => KeyValuePair<K, V>[]

It will return a new array containing tuples of equally positions items from both x and y lists.

The returned list will be truncated to match the length of the shortest supplied list.

const x = [1, 2]
const y = ['A', 'B']
R.zip(x)(y)
// => [[1, 'A'], [2, 'B']]

// truncates to shortest list
R.zip([...x, 3])(['A', 'B'])
// => [[1, 'A'], [2, 'B']]

Try this R.zip example in Rambda REPL

<summary>All TypeScript definitions</summary>
zip<K>(x: K[]): <V>(y: V[]) => KeyValuePair<K, V>[];
<summary>R.zip source</summary>
export function zip(left) {
  return right => {
    const result = []
    const length = Math.min(left.length, right.length)

    for (let i = 0; i < length; i++) {
      result[i] = [left[i], right[i]]
    }

    return result
  }
}
<summary>Tests</summary>
import { zip } from './zip.js'

const array1 = [1, 2, 3]
const array2 = ['A', 'B', 'C']

test('should return an array', () => {
  const actual = zip(array1)(array2)
  expect(actual).toBeInstanceOf(Array)
})

test('should return and array or tuples', () => {
  const expected = [
    [1, 'A'],
    [2, 'B'],
    [3, 'C'],
  ]
  const actual = zip(array1)(array2)
  expect(actual).toEqual(expected)
})

test('should truncate result to length of shorted input list', () => {
  const expectedA = [
    [1, 'A'],
    [2, 'B'],
  ]
  const actualA = zip([1, 2])(array2)
  expect(actualA).toEqual(expectedA)

  const expectedB = [
    [1, 'A'],
    [2, 'B'],
  ]
  const actualB = zip(array1)(['A', 'B'])
  expect(actualB).toEqual(expectedB)
})
<summary>TypeScript test</summary>
import { zip } from 'rambda'

describe('R.zip', () => {
  it('happy', () => {
    const array1 = [1, 2, 3]
    const array2 = ['A', 'B', 'C']
    let a: Partial<any>
    const result = zip(array1)(array2)
    result[0][0] // $ExpectType number
    result[0][1] // $ExpectType string
  })
})

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

zipWith


zipWith<T, U, TResult>(
  fn: (x: T, y: U) => TResult,
  list1: readonly T[],
): (list2: readonly U[]) => TResult[]
const list1 = [ 10, 20, 30, 40 ]
const list2 = [ 100, 200 ]

const result = R.zipWith((x, y) => x + y, list1)(list2)
// => [110, 220]

Try this R.zipWith example in Rambda REPL

<summary>All TypeScript definitions</summary>
zipWith<T, U, TResult>(
  fn: (x: T, y: U) => TResult,
  list1: readonly T[],
): (list2: readonly U[]) => TResult[];
<summary>R.zipWith source</summary>
import { take } from './take.js'

export function zipWith(fn, x) {
  return y =>
    take(x.length > y.length ? y.length : x.length)(x).map((xInstance, i) =>
      fn(xInstance, y[i]),
    )
}
<summary>Tests</summary>
import { zipWith } from './zipWith.js'

const add = (x, y) => x + y
const list1 = [1, 2, 3]
const list2 = [10, 20, 30, 40]
const list3 = [100, 200]

test('when second list is shorter', () => {
  const result = zipWith(add, list1)(list3)
  expect(result).toEqual([101, 202])
})

test('when second list is longer', () => {
  const result = zipWith(add, list1)(list2)
  expect(result).toEqual([11, 22, 33])
})
<summary>TypeScript test</summary>
import { pipe, zipWith } from 'rambda'

const list1 = [1, 2]
const list2 = [10, 20, 30]

describe('R.zipWith', () => {
  it('happy', () => {
    const result = pipe(
      list2,
      zipWith((x, y) => {
        x // $ExpectType number
        y // $ExpectType number
        return `${x}-${y}`
      }, list1),
    )

    result // $ExpectType string[]
  })
})

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

❯ CHANGELOG

11.1.0

  • Add R.filterMap - similar to Ruby filter_map

  • Add R.mapChain - when in R.pipe there are several R.map one after the other, then R.mapChain can be used instead.

  • Add R.middle - equal to R.init + R.tail

  • Add R.random, R.shuffle, R.switcher, R.sum, R.delay - imported from Rambda

  • Add index to R.filter/R.reject predicate signiture

  • Improve typing of R.init, R.tail

11.0.1

  • Add missing JS change for R.includes and R.excludes methods in 11.0.0 release.

11.0.0

  • Breaking change: R.includes and R.excludes now accept list as first argument and value to search as second argument. This makes it more useful when used with R.filter and R.reject.

  • Rename R.innerJoin to R.intersectionWith

  • Add R.unionWith

  • Add R.exists

  • Add R.symmetricDifference

  • Add R.difference

  • R.range now works similar to Ruby's Range - both start and end values are inclusive.

  • Add R.rangeDescending as now R.range works only in ascending order.

  • R.range - it accepts one or two arguments. If one argument is passed, it is considered as end value, and start is 0.

  • R.rangeDescending - it accepts one or two arguments. If one argument is passed, it is considered as start value, and end is 0.

  • Fix R.filter(Boolean) to handle filter of false, not only nullable values.

10.3.4

  • Fix wrong typing for R.sortByDescending - Issue #797

  • Improve R.mapParallelAsync typings to allow optional batchSize parameter.

  • Change order of inputs in R.mapPropObject

  • Change REPL links in documentation

  • Remove jsr.json

10.3.3

  • Fix wrong typing for R.reject - Issue #779

  • Improve R.pick to not allow non-existing keys as input.

10.3.2

  • Fix issue with wrong order of inputs in R.createObjectFromKeys - Issue #779

10.3.1

  • Fix issue with wrong order of inputs in R.propEq - Issue #779

  • Fix issue with TypeScript definitions for R.includes- Issue #781

10.3.0

  • Add R.mapPropObject

  • Add R.duplicateBy

  • Add R.filterAsync

  • Add R.indexBy

  • Restore R.replaceAll

  • Remove option for R.mapAsync to be called outside of R.pipeAsync. This is done for consistency as all other methods follow this rule, i.e. they are all curried.

  • Fix R.pluck to work without R.pipe

  • Remove option for R.mapAsync to be called outside of R.pipeAsync. This is done for consistency as all other methods follow this rule, i.e. they are all curried.

  • Fix R.pluck to work without R.pipe

10.2.0

Add R.modifyPath

10.1.0

  • Add R.assertType and R.convertToType methods

  • Fix issue with exports in old Node.js versions - Discussion #768

  • Fix deno release as it was not possible for users to import version 10.0.0

10.0.1

  • Fix issue with R.unwind/R.pick typings - Issue #766

10.0.0

This is major revamp of Rambda library:

  • R.pipe is the recommended method for TypeScript chaining.

  • All methods should be useful to work inside R.pipe chain. If method doesn't have clear use case inside R.pipe, it is removed as part of this revamp.

  • There will be only one way to use each method. For example, R.add can be used only with R.add(1)(2), i.e. it doesn't support R.add(1, 2). This helps with testing and also with TypeScript definitions. This aligns with TypeScript focused approach of this library.

  • Confusing methods are removed. For example, R.cond and R.ifElse are removed as their usage inside R.piped makes the whole chain less readable. Such logic should be part of your codebase, not part of external library.

  • All methods that expect more than 1 input, will have to be called with R.methodName(input1)(input2) or R.methodName(input1, input2)(input3). This is to make TypeScript definitions easier to maintain.

  • Optimize many methods to better work in TypeScript context with R.pipe. The focus was passing objects through the R.pipe chain.

  • Add R.pipe supports up to 20 functions, i.e. chain can be 20 functions long.

  • R.chain is renamed to R.flatMap

  • R.comparator is renamed to R.sortingFn

  • Remove following methods:

-- Lenses - R.lens, R.lensProp, R.lensPath, R.view, R.set, R.over

-- T, F

-- add

-- addIndex, addIndexRight

-- always

-- ap

-- applySpec

-- applyTo

-- assoc, assocPath, dissoc, dissocPath

-- binary

-- bind

-- call

-- collectBy

-- compose

-- composeWith

-- cond

-- converge

-- curry

-- difference, differenceWith

-- divide, multiply, subtract

-- endsWith/startsWith

-- flip

-- forEachObjIndexed

-- fromPairs

-- gte, lte, lt, gt

-- identical

-- ifElse

-- insert

-- juxt

-- length

-- mapObjIndexed

-- mergeAll, mergeLeft, mergeDeepLeft, mergeDeepRight

-- move

-- partitionIndexed

-- pickAll

-- pickBy

-- repeat

-- splitWhen

-- toLower/toUpper

-- unapply

-- unnest

-- update

-- without

  • Add following methods:

-- R.pipeAsync

-- R.addProp

-- R.createObjectFromKeys

-- R.mapAsync

-- R.mapParallelAsync

-- R.ascend/R.descend

-- R.shuffle

-- R.permutations

-- R.compact

-- R.rejectObject

-- R.findNth

-- R.combinations

-- R.sortByPath

-- R.sortByPathDescending

-- R.sortByDescending

-- R.flattenObject

-- R.addPropToObjects

  • Rename following methods:

-- modifyItemAtIndex -> adjust

-- checkObjectWithSpec -> where

-- objectIncludes -> whereEq

-- modify -> modifyProp

-- chain -> flatMap

-- mapObjIndexed -> mapObject

_ Regarding using object as input with TypeScript in methods such as R.map/filter - this feature is no longer supported in TypeScript as it has multiple issues when using inside pipes. In JS, it still works as before. Following methods are affected:

-- R.map

-- R.mapIndexed

-- R.filter

-- R.reject

  • Regarding using string as path input in R.omit, R.pick and R.path with TypeScript - now it require explicit definition of expected return type.

  • Revert adding stopper logic in R.reduce - https://github.com/selfrefactor/rambda/pull/630

  • Remove use of Dictionary custom interface and use more appropriate Record<PropertyType, ...>

  • Remove use of Record<string, ...> in favour of Record<PropertyType, ...>

  • Add TypeScript definition to handle common case of R.filter(Boolean) that will turn Array<T | undefined> to Array<T>.

  • Regarding using object with R.forEach in TypeScript - this is no longer supported. Again, JS version still works with objects.

  • head/last - empty array as input will return undefined, but never

  • assocPath - stop supporting curring of type (x)(y)(z)

  • Stop support string inputs for some methods, since it was hard to correctly type them in TypeScript.

-- append/prepend

  • Change R.range to work with descending order.

  • Remove rambda/immutable as import option as it is hard to support in the new context.

  • Sync with typing of @types/ramda:

-- allPass

-- anyPass

-- append

-- both

-- countBy

-- drop

-- dropLast

-- dropRepeatsBy

-- either

-- filter

-- forEach

-- keys

-- map

-- mergeAll

-- modify

-- modifyPath

-- omit

-- partition

-- pluck

-- prepend

-- propEq

-- where

-- whereAny

  • Sync with typing of remeda:

-- filter

-- reject

-- map

-- mapObject

-- toPairs

-- partition

-- objOf

-- pluck

-- mergeWith

9.4.2

  • Fix TS issue when R.take is used as part of R.pipe.

Moving away from Ramda types which are problematic in this case:

const data = ['foo', 'bar', 'baz', 'qux']
const result = piped(
	data,
	filter(
		x => x.length >= 2
	),
	takeLast(2),
)

9.4.1

  • Fix bug with R.differenceWith when two arrays has same length - Issue #757

  • Allow path input to not be transformed when string numbers are there - Issue #750

9.4.0

  • Fix deno release

  • Fix too strict true condition in R.ifElse - Issue #750

  • Change R.groupBy typings to match @types/ramda typings

9.3.0

  • Breaking change in relation to TS typings of R.assoc, R.dissoc and R.modify - https://github.com/ramda/types/pull/37

  • Add R.isNotEmpty as it is new method in Ramda

  • Fix R.head/R.last TS definition - It returns undefined if array has length of 0. Before

9.2.1

9.2.0

9.1.1

9.1.0

Add these methods

  • insert
  • insertAll
  • lt
  • lte
  • isNotNil
  • pickBy
  • pathSatisfies
  • swap
  • mergeDeepLeft

9.0.1

  • Fix bad TS typings, due to missing declaration - Issue #716

9.0.0

Breaking change in TS definitions of lenses as now they are synced to Ramda types.

  • Add R.sortWith - Issue #707

  • Add R.innerJoin, R.gt, R.gte, R.reduceBy, R.hasIn

8.6.0

  • Wrong typing for R.dissocPath - Issue #709

  • Update build dependencies

8.5.0

  • Revert changes in R.anyPass introduced in 8.4.0 release. The reason is that the change was breaking the library older than 5.2.0 TypeScript.

  • Wrong R.partial TS definition - Issue #705

  • Add R.dropRepeatsBy

  • Add R.empty

  • Add R.eqBy

  • Add R.forEachObjIndexed

8.4.0

  • Add R.dissocPath

  • Fix TS definitions of R.head/R.last and add missing handle of empty string

  • Add R.removeIndex - method was before only in Rambdax, but now since R.dissocPath is using it, it is added to main library.

  • Allow R.omit to pass numbers as part of properties to omit, i.e. R.omit(['a', 1], {a: {1: 1, 2: 2}})

  • R.keys always returns strings - MR #700

  • Improve R.prepend/R.append type interference - MR #699

  • Change R.reduce TS definitions so index is always received - MR #696

  • Functions as a type guard in R.anyPass TS definitions - MR #695

  • Fix R.append's curried type - MR #694

  • Fix cannot compare errors in Deno with R.equals - Issue #704.

  • Fix cannot compare BigInt with R.equals

8.3.0

Add the following methods:

  • binary
  • call
  • collectBy
  • comparator
  • composeWith

8.2.0

Add the following methods:

  • addIndex
  • addIndexRight
  • ap
  • aperture
  • applyTo
  • ascend
  • descend

8.1.0

  • Fix input order of TS definitions for R.propEq method - Issue #688. The issue was due to 8.0.0 was shipped with TS definitions of 7.5.0 release.

  • Add R.differenceWith method - Issue #91

8.0.0

  • handle falsy values in merge methods - https://github.com/ramda/ramda/pull/3222

  • R.head/R.last don't return undefined for non-empty arrays

  • R.type supports dates in TS definition - Rambda already did support dates in JS.

  • Improve typings of R.endsWith/startsWith with regard to string input. - PR #622

  • Handle list as falsy value in R.reduce - Ramda MR

  • R.nop is removed - it will be moved to Rambdax as R.noop

  • R.includes is no longer using string literal in TypeScript definitions

Reason for breaking change - synchronize with Ramda 0.29.0 release:

This is only part of the changelog. You can read the full text in CHANGELOG.md file.

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

❯ Additional info

Most influential contributors(in alphabetical order)

  • @farwayer - improving performance in R.find, R.filter; give the idea how to make benchmarks more reliable;

  • @thejohnfreeman - add R.assoc, R.chain;

  • @peeja - add several methods and fix mutiple issues; provides great MR documentation

  • @helmuthdu - add R.clone; help improve code style;

  • @jpgorman - add R.zip, R.reject, R.without, R.addIndex;

  • @ku8ar - add R.slice, R.propOr, R.identical, R.propIs and several math related methods; introduce the idea to display missing Ramda methods;

  • @romgrk - add R.groupBy, R.indexBy, R.findLast, R.findLastIndex;

  • @squidfunk - add R.assocPath, R.symmetricDifference, R.difference, R.intersperse;

  • @synthet1c - add all lenses methods; add R.applySpec, R.converge;

  • @vlad-zhukov - help with configuring Rollup, Babel; change export file to use ES module exports;

Rambda references

Links to Rambda

Deprecated from Used by section

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

My other libraries

Niketa theme

Collection of 9 light VSCode themes

Niketa dark theme

Collection of 9 dark VSCode themes

String-fn

String utility library

Useful Javascript libraries

Large collection of JavaScript,TypeScript and Angular related repos links

Run-fn

CLI commands for lint JS/TS files, commit git changes and upgrade of dependencies

Stargazers over time

Stargazers over time

Current Tags

  • 6.7.0-beta.0                                ...           beta (5 years ago)
  • 10.3.2                                ...           latest (8 months ago)

215 Versions

  • 10.3.2                                ...           8 months ago
  • 10.3.1                                ...           9 months ago
  • 10.3.0                                ...           9 months ago
  • 10.2.0                                ...           a year ago
  • 10.1.0                                ...           a year ago
  • 10.0.1                                ...           a year ago
  • 10.0.0                                ...           a year ago
  • 10.0.0-beta.3                                ...           a year ago
  • 10.0.0-beta.1                                ...           a year ago
  • 10.0.0-alpha.0                                ...           a year ago
  • 9.4.2                                ...           a year ago
  • 9.4.1                                ...           a year ago
  • 9.4.0                                ...           a year ago
  • 9.3.0                                ...           2 years ago
  • 9.2.1                                ...           2 years ago
  • 9.2.0                                ...           2 years ago
  • 9.1.1                                ...           2 years ago
  • 9.1.0                                ...           2 years ago
  • 9.0.1                                ...           2 years ago
  • 9.0.0                                ...           2 years ago
  • 8.6.0                                ...           2 years ago
  • 8.5.0                                ...           3 years ago
  • 8.4.0                                ...           3 years ago
  • 8.3.0                                ...           3 years ago
  • 8.2.0                                ...           3 years ago
  • 8.1.0                                ...           3 years ago
  • 8.0.0                                ...           3 years ago
  • 7.5.0                                ...           3 years ago
  • 7.4.0                                ...           3 years ago
  • 7.3.0                                ...           4 years ago
  • 7.2.1                                ...           4 years ago
  • 7.2.0                                ...           4 years ago
  • 7.1.4                                ...           4 years ago
  • 7.1.3                                ...           4 years ago
  • 7.1.2                                ...           4 years ago
  • 7.1.1                                ...           4 years ago
  • 7.1.0                                ...           4 years ago
  • 7.0.3                                ...           4 years ago
  • 7.0.2                                ...           4 years ago
  • 7.0.1                                ...           4 years ago
  • 7.0.0                                ...           4 years ago
  • 6.9.0                                ...           5 years ago
  • 6.8.3                                ...           5 years ago
  • 6.8.2                                ...           5 years ago
  • 6.8.1                                ...           5 years ago
  • 6.8.0                                ...           5 years ago
  • 6.7.0                                ...           5 years ago
  • 6.7.0-beta.0                                ...           5 years ago
  • 6.6.0                                ...           5 years ago
  • 6.5.3                                ...           5 years ago
  • 6.5.2                                ...           5 years ago
  • 6.5.1                                ...           5 years ago
  • 6.5.0                                ...           5 years ago
  • 6.4.0                                ...           5 years ago
  • 6.3.1                                ...           5 years ago
  • 6.3.0                                ...           5 years ago
  • 6.2.0                                ...           6 years ago
  • 6.1.0                                ...           6 years ago
  • 6.0.1                                ...           6 years ago
  • 6.0.0                                ...           6 years ago
  • 5.13.1                                ...           6 years ago
  • 5.13.0                                ...           6 years ago
  • 5.12.1                                ...           6 years ago
  • 5.12.0                                ...           6 years ago
  • 5.11.0                                ...           6 years ago
  • 5.10.0                                ...           6 years ago
  • 5.9.0                                ...           6 years ago
  • 5.8.0                                ...           6 years ago
  • 5.7.0                                ...           6 years ago
  • 5.6.3                                ...           6 years ago
  • 5.6.2                                ...           6 years ago
  • 5.6.1                                ...           6 years ago
  • 5.6.0                                ...           6 years ago
  • 5.5.0                                ...           6 years ago
  • 5.4.3                                ...           6 years ago
  • 5.4.2                                ...           6 years ago
  • 5.4.1                                ...           6 years ago
  • 5.4.0                                ...           6 years ago
  • 5.3.0                                ...           6 years ago
  • 5.2.1                                ...           6 years ago
  • 5.2.0                                ...           6 years ago
  • 5.1.1                                ...           6 years ago
  • 5.1.0                                ...           6 years ago
  • 5.0.0                                ...           6 years ago
  • 4.6.0                                ...           6 years ago
  • 4.5.0                                ...           6 years ago
  • 4.4.0                                ...           6 years ago
  • 4.3.0                                ...           6 years ago
  • 4.2.0                                ...           6 years ago
  • 4.1.0                                ...           6 years ago
  • 4.0.2                                ...           6 years ago
  • 4.0.1                                ...           6 years ago
  • 4.0.0                                ...           6 years ago
  • 3.3.0                                ...           6 years ago
  • 3.2.5                                ...           6 years ago
  • 3.2.1                                ...           7 years ago
  • 3.2.0                                ...           7 years ago
  • 3.1.1                                ...           7 years ago
  • 3.1.0                                ...           7 years ago
  • 3.0.1                                ...           7 years ago
  • 3.0.0                                ...           7 years ago
  • 2.14.5                                ...           7 years ago
  • 2.14.4                                ...           7 years ago
  • 2.14.3                                ...           7 years ago
  • 2.14.2                                ...           7 years ago
  • 2.14.1                                ...           7 years ago
  • 2.14.0                                ...           7 years ago
  • 2.13.1                                ...           7 years ago
  • 2.13.0                                ...           7 years ago
  • 2.12.0                                ...           7 years ago
  • 2.11.2                                ...           7 years ago
  • 2.11.1                                ...           7 years ago
  • 2.11.0                                ...           7 years ago
  • 2.10.2                                ...           7 years ago
  • 2.10.1                                ...           7 years ago
  • 2.10.0                                ...           7 years ago
  • 2.9.0                                ...           7 years ago
  • 2.8.0                                ...           7 years ago
  • 2.7.1                                ...           7 years ago
  • 2.7.0                                ...           7 years ago
  • 2.6.0                                ...           7 years ago
  • 2.5.0                                ...           7 years ago
  • 2.4.1                                ...           7 years ago
  • 2.4.0                                ...           7 years ago
  • 2.3.2                                ...           7 years ago
  • 2.3.1                                ...           7 years ago
  • 2.3.0                                ...           7 years ago
  • 2.2.0                                ...           7 years ago
  • 2.1.1                                ...           7 years ago
  • 2.1.0                                ...           7 years ago
  • 2.0.0                                ...           7 years ago
  • 1.2.6                                ...           7 years ago
  • 1.2.5                                ...           7 years ago
  • 1.2.4                                ...           7 years ago
  • 1.2.2                                ...           8 years ago
  • 1.2.1                                ...           8 years ago
  • 1.2.0                                ...           8 years ago
  • 1.1.5                                ...           8 years ago
  • 1.1.4                                ...           8 years ago
  • 1.1.3                                ...           8 years ago
  • 1.1.2                                ...           8 years ago
  • 1.1.1                                ...           8 years ago
  • 1.1.0                                ...           8 years ago
  • 1.0.13                                ...           8 years ago
  • 1.0.12                                ...           8 years ago
  • 1.0.11                                ...           8 years ago
  • 1.0.10                                ...           8 years ago
  • 1.0.9                                ...           8 years ago
  • 1.0.8                                ...           8 years ago
  • 1.0.7                                ...           8 years ago
  • 1.0.6                                ...           8 years ago
  • 1.0.5                                ...           8 years ago
  • 1.0.4                                ...           8 years ago
  • 1.0.3                                ...           8 years ago
  • 1.0.0                                ...           8 years ago
  • 0.9.8                                ...           8 years ago
  • 0.9.7                                ...           8 years ago
  • 0.9.6                                ...           8 years ago
  • 0.9.5                                ...           8 years ago
  • 0.9.4                                ...           9 years ago
  • 0.9.3                                ...           9 years ago
  • 0.9.2                                ...           9 years ago
  • 0.9.1                                ...           9 years ago
  • 0.8.10                                ...           9 years ago
  • 0.9.0                                ...           9 years ago
  • 0.8.9                                ...           9 years ago
  • 0.8.8                                ...           9 years ago
  • 0.8.7                                ...           9 years ago
  • 0.8.6                                ...           9 years ago
  • 0.8.5                                ...           9 years ago
  • 0.8.4                                ...           9 years ago
  • 0.8.3                                ...           9 years ago
  • 0.8.2                                ...           9 years ago
  • 0.8.1                                ...           9 years ago
  • 0.8.0                                ...           9 years ago
  • 0.7.6                                ...           9 years ago
  • 0.7.5                                ...           9 years ago
  • 0.7.4                                ...           9 years ago
  • 0.7.3                                ...           9 years ago
  • 0.7.2                                ...           9 years ago
  • 0.7.1                                ...           9 years ago
  • 0.7.0                                ...           9 years ago
  • 0.6.1                                ...           9 years ago
  • 0.5.13                                ...           9 years ago
  • 0.6.0                                ...           9 years ago
  • 0.5.12                                ...           9 years ago
  • 0.5.11                                ...           9 years ago
  • 0.5.10                                ...           9 years ago
  • 0.5.9                                ...           9 years ago
  • 0.5.8                                ...           9 years ago
  • 0.5.7                                ...           9 years ago
  • 0.5.6                                ...           9 years ago
  • 0.5.5                                ...           9 years ago
  • 0.5.4                                ...           9 years ago
  • 0.5.3                                ...           9 years ago
  • 0.5.2                                ...           9 years ago
  • 0.5.1                                ...           9 years ago
  • 0.5.0                                ...           9 years ago
  • 0.4.3                                ...           9 years ago
  • 0.4.2                                ...           9 years ago
  • 0.4.1                                ...           9 years ago
  • 0.4.0                                ...           9 years ago
  • 0.3.4                                ...           9 years ago
  • 0.3.3                                ...           9 years ago
  • 0.3.2                                ...           9 years ago
  • 0.3.1                                ...           9 years ago
  • 0.3.0                                ...           9 years ago
  • 0.2.1                                ...           9 years ago
  • 0.2.0                                ...           9 years ago
  • 0.1.5                                ...           9 years ago
  • 0.1.4                                ...           9 years ago
  • 0.1.3                                ...           9 years ago
  • 0.1.2                                ...           9 years ago
  • 0.1.1                                ...           9 years ago
  • 0.1.0                                ...           9 years ago
Maintainers (1)
Downloads
Today 0
This Week 2
This Month 180
Last Day 0
Last Week 178
Last Month 0
Dependencies (0)
None
Dev Dependencies (14)

Copyright 2013 - present © cnpmjs.org | Home |