rambda
v10.3.4
Published
Lightweight and faster alternative to Ramda with included TS definitions
Downloads
11,501,049
Maintainers
Readme
Rambda
Rambda is TypeScript-focused utility library similar to Remeda, Ramda and Radashi. - Documentation site
❯ 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.pipechainThis 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.innerJoinis kept, butR.identical,R.moveis removed. Methods such asR.toLower,R.lengthprovide little value. Such method are omitted from Rambda on purpose.Some generic methods such as
curryandassocis not easy to be expressed in TypeScript. For this reasonRambdaomits such methods.No
R.condorR.ifElseas they make the chain less readable.No
R.lengthas it adds very little value.No
R.differenceas 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 propertiesDifferences 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
addProp<T extends object, P extends PropertyKey, V extends unknown>(
prop: P,
value: V
): (obj: T) => MergeTypes<T & Record<P, V>>;export function addProp(key, value) {
return obj => ({ ...obj, [key]: value })
}import { addProp } from "./addProp.js"
test('happy', () => {
const result = addProp('a', 1)({ b: 2 })
const expected = { a: 1, b: 2 }
expect(result).toEqual(expected)
})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
addPropToObjects<
T extends object,
K extends string,
R
>(
property: K,
fn: (input: T) => R
): (list: T[]) => MergeTypes<T & { [P in K]: R }>[];import { mapFn } from './map.js'
export function addPropToObjects (
property,
fn
){
return listOfObjects => mapFn(
(obj) => ({
...(obj),
[property]: fn(obj)
}),
listOfObjects
)
}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' },
])
})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[]) => booleanIt 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)
) // => trueTry this R.all example in Rambda REPL
all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;export function all(predicate) {
return list => {
for (let i = 0; i < list.length; i++) {
if (!predicate(list[i])) {
return false
}
}
return true
}
}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()
})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[]): FIt 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
allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F;export function allPass(predicates) {
return input => {
let counter = 0
while (counter < predicates.length) {
if (!predicates[counter](input)) {
return false
}
counter++
}
return true
}
}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([])
})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[]) => booleanIt 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)
// => trueTry this R.any example in Rambda REPL
any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;export function any(predicate) {
return list => {
let counter = 0
while (counter < list.length) {
if (predicate(list[counter], counter)) {
return true
}
counter++
}
return false
}
}import { any } from './any.js'
const list = [1, 2, 3]
test('happy', () => {
expect(any(x => x > 2)(list)).toBeTruthy()
})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 | TF2It 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)
// => trueTry this R.anyPass example in Rambda REPL
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;
...
...export function anyPass(predicates) {
return input => {
let counter = 0
while (counter < predicates.length) {
if (predicates[counter](input)) {
return true
}
counter++
}
return false
}
}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()
})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 result = R.append('foo')(['bar', 'baz'])
// => ['bar', 'baz', 'foo']Try this R.append example in Rambda REPL
append<T>(el: T): (list: T[]) => T[];
append<T>(el: T): (list: readonly T[]) => T[];import { cloneList } from './_internals/cloneList.js'
export function append(x) {
return list => {
const clone = cloneList(list)
clone.push(x)
return clone
}
}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'])
})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)=> OrderingHelper 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
ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;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)
}
}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}])
})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) => UIt 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.
assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U;export function assertType(fn) {
return (x) => {
if (fn(x)) {
return x
}
throw new Error('type assertion failed in R.assertType')
}
}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')
})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) => booleanIt 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)
// => trueTry this R.checkObjectWithSpec example in Rambda REPL
checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean;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
}
}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)
})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
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>
};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)
}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 })
})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) => booleanIt 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
complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean;export function complement(fn) {
return (...input) => !fn(...input)
}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()
})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
concat<T>(x: T[]): (y: T[]) => T[];
concat(x: string): (y: string) => string;export function concat(x) {
return y => (typeof x === 'string' ? `${x}${y}` : [...x, ...y])
}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) : TIt helps to convert a value to a specific type. It is useful when you have to overcome TypeScript's type inference.
convertToType<T>(x: unknown) : T;export function convertToType(x) {
return x
}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[]) => numberIt 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)
// => 2Try this R.count example in Rambda REPL
count<T>(predicate: (x: T) => boolean): (list: T[]) => number;import { isArray } from './_internals/isArray.js'
export function count(predicate) {
return list => {
if (!isArray(list)) {
return 0
}
return list.filter(x => predicate(x)).length
}
}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)
})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
countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number };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
}
}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,
})
})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
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 };export function createObjectFromKeys(fn) {
return keys => {
const result = {}
keys.forEach((key, index) => {
result[key] = fn(key, index)
})
return result
}
}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) => TIt 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
defaultTo<T>(defaultValue: T): (input: unknown) => T;function isFalsy(input) {
return input === undefined || input === null || Number.isNaN(input) === true
}
export function defaultTo(defaultArgument) {
return input => isFalsy(input) ? defaultArgument : input
}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')
})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)=> OrderingHelper 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
descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;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
drop<T>(howMany: number): (list: T[]) => T[];export function drop(howManyToDrop, ) {
return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0)
}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])
})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.
dropLast<T>(howMany: number): (list: T[]) => T[];export function dropLast(numberItems) {
return list => (numberItems > 0 ? list.slice(0, -numberItems) : list.slice())
}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
dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropLastWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];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()
}
}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
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
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
dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[];
dropWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];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
}
}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)
})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
duplicateBy<T, U>(fn: (x: T) => U): (list: T[]) => T[];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)))
}
}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) => booleanconst result = R.eqBy(Math.abs, 5)(-5)
// => trueTry this R.eqBy example in Rambda REPL
eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean;import { equalsFn } from './equals.js'
export function eqBy(fn, a) {
return b => equalsFn(fn(a), fn(b))
}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) => booleanIt 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)
// => trueTry this R.eqProps example in Rambda REPL
eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean;import { equalsFn } from './equals.js'
export function eqProps(property, objA) {
return objB => equalsFn( objA[property], objB[property] )
}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()
})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): booleanIt 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}]]
) // => trueTry this R.equals example in Rambda REPL
equals<T>(x: T, y: T): boolean;
equals<T>(x: T): (y: T) => boolean;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)
}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 obj
