Rambda
Rambda
is TypeScript-focused utility library similar to Remeda
, Ramda
and Radashi
.
Initially it started as faster alternative to functional programming library Ramda
, but in order to address many TypeScript issues, now Rambda
takes a separate path. - Documentation
❯ Example use
import { pipe, map, filter } 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
chainThis 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);
});
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, butR.identical
,R.move
is removed. Methods such asR.toLower
,R.length
provide little value. Such method are omitted from Rambda on purpose.Some generic methods such as
curry
andassoc
is not easy to be expressed in TypeScript. For this reasonRambda
omits such methods.No
R.cond
orR.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
Fast performance compared to Ramda
Since Rambda
methods doesn't use so many internals, it is faster than Ramda
.
Prior to version 10
, benchmark summary was included, but now the main selling point is the TypeScript focus, not performance so this is no longer included.
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.
Documentation site of Rambda
version 9.4.2
is available here.
From version 10.0.0
onwards, Rambda will start to diverge from Ramda in order to address some of the issues that Ramda has.
Currently, Rambda
includes 32 methods that differ from Ramda
and shares 83 methods with it.
R.forEach
.
-- Naming of methods that doesn't match developer's expectation, such as R.chain
, which should be called flatMap
.
-- Naming of methods is sometimes too generic to be remembered such as R.update
, R.modify
, R.where
.
-- Methods that are already present in standard JavaScript, such as R.toLower
, R.length
.
-- R.compose
doesn't have the best possible TypeScript support.
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
typescript
addProp<T extends object, P extends PropertyKey, V extends unknown>(
prop: P,
value: V
): (obj: T) => MergeTypes<T & Record<P, V>>;
javascript
export function addProp(key, value) {
return obj => ({ ...obj, [key]: value })
}
javascript
import { addProp } from "./addProp.js"
test('happy', () => {
const result = addProp('a', 1)({ b: 2 })
const expected = { a: 1, b: 2 }
expect(result).toEqual(expected)
})
typescript
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
typescript
addPropToObjects<
T extends object,
K extends string,
R
>(
property: K,
fn: (input: T) => R
): (list: T[]) => MergeTypes<T & { [P in K]: R }>[];
javascript
import { mapFn } from './map.js'
export function addPropToObjects (
property,
fn
){
return listOfObjects => mapFn(
(obj) => ({
...(obj),
[property]: fn(obj)
}),
listOfObjects
)
}
javascript
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' },
])
})
typescript
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
typescript
all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
javascript
export function all(predicate) {
return list => {
for (let i = 0; i < list.length; i++) {
if (!predicate(list[i])) {
return false
}
}
return true
}
}
javascript
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()
})
typescript
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
typescript
allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F;
javascript
export function allPass(predicates) {
return input => {
let counter = 0
while (counter < predicates.length) {
if (!predicates[counter](input)) {
return false
}
counter++
}
return true
}
}
javascript
import { allPass } from './allPass.js'
import { filter } from './filter.js'
import { includes } from './includes.js'
import { pipe } from './pipe.js'
const list = [
[1, 2, 3, 4],
[3, 4, 5],
]
test('happy', () => {
const result = pipe(list, filter(allPass([includes(2), includes(3)])))
expect(result).toEqual([[1, 2, 3, 4]])
})
test('when returns false', () => {
const result = pipe(list, filter(allPass([includes(12), includes(31)])))
expect(result).toEqual([])
})
typescript
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([R.includes(3), R.includes(4)])))
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(fn)(list)
// => true
Try this R.any example in Rambda REPL
typescript
any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
javascript
export function any(predicate) {
return list => {
let counter = 0
while (counter < list.length) {
if (predicate(list[counter], counter)) {
return true
}
counter++
}
return false
}
}
javascript
import { any } from './any.js'
const list = [1, 2, 3]
test('happy', () => {
expect(any(x => x > 2)(list)).toBeTruthy()
})
typescript
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
typescript
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;
...
...
javascript
export function anyPass(predicates) {
return input => {
let counter = 0
while (counter < predicates.length) {
if (predicates[counter](input)) {
return true
}
counter++
}
return false
}
}
javascript
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()
})
typescript
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: T[]) => T[]
It adds element x
at the end of iterable
.
const x = 'foo'
const result = R.append(x, ['bar', 'baz'])
// => ['bar', 'baz', 'foo']
Try this R.append example in Rambda REPL
typescript
append<T>(el: T): (list: T[]) => T[];
append<T>(el: T): (list: readonly T[]) => T[];
javascript
import { cloneList } from './_internals/cloneList.js'
export function append(x) {
return list => {
const clone = cloneList(list)
clone.push(x)
return clone
}
}
javascript
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'])
})
typescript
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
typescript
ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
javascript
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)
}
}
javascript
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}])
})
typescript
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.
typescript
assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U;
javascript
export function assertType(fn) {
return (x) => {
if (fn(x)) {
return x
}
throw new Error('type assertion failed in R.assertType')
}
}
javascript
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')
})
typescript
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
typescript
checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean;
javascript
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
}
}
javascript
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)
})
typescript
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
typescript
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>
};
javascript
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)
}
javascript
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 })
})
typescript
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
typescript
complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean;
javascript
export function complement(fn) {
return (...input) => !fn(...input)
}
javascript
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()
})
typescript
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
typescript
concat<T>(x: T[]): (y: T[]) => T[];
concat(x: string): (y: string) => string;
javascript
export function concat(x) {
return y => (typeof x === 'string' ? `${x}${y}` : [...x, ...y])
}
typescript
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.
typescript
convertToType<T>(x: unknown) : T;
javascript
export function convertToType(x) {
return x
}
typescript
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
typescript
count<T>(predicate: (x: T) => boolean): (list: T[]) => number;
javascript
import { isArray } from './_internals/isArray.js'
export function count(predicate) {
return list => {
if (!isArray(list)) {
return 0
}
return list.filter(x => predicate(x)).length
}
}
javascript
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)
})
typescript
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
typescript
countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number };
javascript
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
}
}
javascript
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,
})
})
typescript
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
typescript
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 };
javascript
export function createObjectFromKeys(keys) {
return fn => {
const result = {}
keys.forEach((key, index) => {
result[key] = fn(key, index)
})
return result
}
}
javascript
import { createObjectFromKeys } from './createObjectFromKeys.js'
test('happy', () => {
const result = createObjectFromKeys(['a', 'b'])((key, index) => key.toUpperCase() + index)
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
typescript
defaultTo<T>(defaultValue: T): (input: unknown) => T;
javascript
function isFalsy(input) {
return input === undefined || input === null || Number.isNaN(input) === true
}
export function defaultTo(defaultArgument) {
return input => isFalsy(input) ? defaultArgument : input
}
javascript
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')
})
typescript
import { defaultTo, pipe } from 'rambda'
describe('R.defaultTo', () => {
it('happy', () => {
const result = pipe('bar' as unknown, defaultTo('foo'))
result // $ExpectType string
})
})
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
typescript
descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
javascript
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)
}
}
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
typescript
drop<T>(howMany: number): (list: T[]) => T[];
javascript
export function drop(howManyToDrop, ) {
return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0)
}
javascript
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])
})
typescript
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.
typescript
dropLast<T>(howMany: number): (list: T[]) => T[];
javascript
export function dropLast(numberItems) {
return list => (numberItems > 0 ? list.slice(0, -numberItems) : list.slice())
}
javascript
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
typescript
dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropLastWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
javascript
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()
}
}
javascript
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
typescript
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
typescript
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
typescript
dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
javascript
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
}
}
javascript
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)
})
typescript
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[]
})
})
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
typescript
eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean;
javascript
import { equalsFn } from './equals.js'
export function eqBy(fn, a) {
return b => equalsFn(fn(a), fn(b))
}
javascript
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
typescript
eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean;
javascript
import { equalsFn } from './equals.js'
export function eqProps(property, objA) {
return objB => equalsFn( objA[property], objB[property] )
}
javascript
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()
})
typescript
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
typescript
equals<T>(x: T, y: T): boolean;
equals<T>(x: T): (y: T) => boolean;
javascript
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)
}
```javascript 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(e