rambda v10.0.0-alpha.0
Rambda
Rambda
is TypeScript-focused alternative to the popular functional programming library Ramda alternative. It also has better speed and smaller size. - Documentation
❯ Example use
import { piped, map, filter } from 'rambda'
const result = piped(
[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 advantages
TypeScript included
TypeScript definitions are included in the library, in comparison to Ramda, where you need to additionally install @types/ramda
.
Still, you need to be aware that functional programming features in TypeScript
are in development, which means that using R.compose/R.pipe can be problematic.
undefined
Understandable source code due to little usage of internals
Ramda
uses a lot of internals, which hides a lot of logic. Reading the full source code of a method can be challenging.
Better VSCode experience
If the project is written in Javascript, then go to source definition
action will lead you to actual implementation of the method.
Immutable TS definitions
You can use immutable version of Rambda definitions, which is linted with ESLint functional/prefer-readonly-type
plugin.
import {add} from 'rambda/immutable'
Deno support
Latest version of Ramba available for Deno
users is 3 years old. This is not the case with Rambda as most of recent releases are available for Deno
users.
Also, Rambda
provides you with included TS definitions:
// Deno extension(https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno)
// is installed and initialized
import * as R from "https://deno.land/x/rambda/mod.ts";
import * as Ramda from "https://deno.land/x/ramda/mod.ts";
R.add(1)('foo') // => will trigger warning in VSCode as it should
Ramda.add(1)('foo') // => will not trigger warning in VSCode
Dot notation for R.path
, R.paths
, R.assocPath
and R.lensPath
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']
.
The other side effect is in R.assocPath
and R.dissocPath
, where inputs such as ['a', '1', 'b']
will be turned into ['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
Speed
Rambda is generally more performant than Ramda
as the benchmarks can prove that.
Support
One of the main issues with Ramda
is the slow process of releasing new versions. This is not the case with Rambda as releases are made on regular basis.
❯ Install
yarn add rambda
For UMD usage either use
./dist/rambda.umd.js
or the following CDN link:
https://unpkg.com/rambda@CURRENT_VERSION/dist/rambda.umd.js
- with deno
import {add} from "https://deno.land/x/rambda/mod.ts";
Differences between Rambda and Ramda
Rambda's type detects async functions and unresolved
Promises
. The returned values are'Async'
and'Promise'
.Rambda's type handles NaN input, in which case it returns
NaN
.Rambda's forEach can iterate over objects not only arrays.
Rambda's map, filter, partition when they iterate over objects, they pass property and input object as predicate's argument.
Rambda's filter returns empty array with bad input(
null
orundefined
), while Ramda throws.Ramda's clamp work with strings, while Rambda's method work only with numbers.
Ramda's indexOf/lastIndexOf work with strings and lists, while Rambda's method work only with lists as iterable input.
Error handling, when wrong inputs are provided, may not be the same. This difference will be better documented once all brute force tests are completed.
TypeScript definitions between
rambda
and@types/ramda
may vary.
Benchmarks
TODO
API
add
It adds a
and b
.
const result = R.pipe(
2,
R.add(3)
) // => 5
Try this R.add example in Rambda REPL
all
all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean
It returns true
, if all members of array list
returns true
, when applied as argument to predicate
function.
const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > -1
const result = R.pipe(
list,
R.all(predicate)
) // => true
Try this R.all example in Rambda REPL
all<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[]): 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
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 { pipe } from './pipe.js'
import { filter } from './filter.js'
import { includes } from './includes.js'
import { allPass } from './allPass.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[]) => 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
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', () => {
let 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
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;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 extends T, TF5 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 TF5
],
): (a: T) => a is TF1 | TF2 | TF3 | TF4 | TF5;
anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 extends T, TF5 extends T, TF6 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 TF5,
(a: T) => a is TF6
],
): (a: T) => a is TF1 | TF2 | TF3 | TF4 | TF5 | TF6;
anyPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F;
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 + curry', () => {
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 x = 'foo'
const result = R.append(x, ['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 {pipe,append, 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; }[]
})
})
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
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
})
})
complement
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 origin = x => x > 5
const inverted = complement(origin)
const result = [
origin(7),
inverted(7)
] => [ true, false ]
Try this R.complement example in Rambda REPL
concat
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
count
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
countBy
countBy<T>(fn: (a: 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(R.toLower, 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: (a: 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', () => {
let result = pipe(
list,
countBy((x) => x.toLowerCase())
)
result.a // $ExpectType number
result.foo // $ExpectType number
result // $ExpectType { [index: string]: number; }
})
dec
It decrements a number.
const result = R.dec(2) // => 1
Try this R.dec example in Rambda REPL
defaultTo
defaultTo<T>(defaultValue: T, input: T | null | undefined): 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).
R.defaultTo('foo', 'bar') // => 'bar'
R.defaultTo('foo', undefined) // => 'foo'
// Important - emtpy string is not falsy value(same as Ramda)
R.defaultTo('foo', '') // => 'foo'
Try this R.defaultTo example in Rambda REPL
defaultTo<T>(defaultValue: T, input: T | null | undefined): T;
defaultTo<T>(defaultValue: T): <U>(input: U | null | undefined) => EqualTypes<U, T> extends true ? T : never
function isFalsy(input) {
return input === undefined || input === null || Number.isNaN(input) === true
}
export function defaultTo(defaultArgument, input) {
if (arguments.length === 1) {
return _input => defaultTo(defaultArgument, _input)
}
return 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 } from 'rambda'
describe('R.defaultTo with Ramda spec', () => {
it('happy', () => {
const result = defaultTo('foo', '')
result // $ExpectType "" | "foo"
})
it('with explicit type', () => {
const result = defaultTo<string>('foo', null)
result // $ExpectType string
})
})
drop
drop<T>(howMany: number): (list: T[]) => T[]
It returns howMany
items dropped from beginning of list or string input
.
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, listOrString) {
if (arguments.length === 1) {
return _list => drop(howManyToDrop, _list)
}
return listOrString.slice(howManyToDrop > 0 ? howManyToDrop : 0)
}
import assert from 'node:assert'
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 string', () => {
expect(drop(3, 'rambda')).toBe('bda')
})
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])
})
test('should return copy', () => {
const xs = [1, 2, 3]
assert.notStrictEqual(drop(0, xs), xs)
assert.notStrictEqual(drop(-1, xs), xs)
})
import { drop, pipe } from 'rambda'
it('R.drop', () => {
let 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 or string input
.
R.dropLast(2)(['foo', 'bar', 'baz']) // => ['foo']
Try this R.dropLast example in Rambda REPL
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
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
dropRepeatsBy
const result = R.dropRepeatsBy(
Math.abs,
[1, -1, 2, 3, -3]
)
// => [1, 2, 3]
Try this R.dropRepeatsBy example in Rambda REPL
dropRepeatsWith
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
dropWhile
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
eqBy
const result = R.eqBy(Math.abs, 5)(-5)
// => true
Try this R.eqBy example in Rambda REPL
eqProps
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
equals
equals<T>(x: T, y: T): boolean
It deeply compares x
and y
and returns true
if they are equal.
R.equals(
[1, {a:2}, [{b: 3}]],
[1, {a:2}, [{b: 3}]]
) // => true
Try 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 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()
})
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<E extends Evolver>(rules: E): <V extends Evolvable<E>>(obj: V) => Evolve<V, E>
It takes object of functions as set of rules. These rules
are applied to the iterable
input to produce the result.
const rules = {
foo : add(1),
bar : add(-1),
}
const input = {
a : 1,
foo : 2,
bar : 3,
}
const result = R.evolve(rules)(input)
const expected = {
a : 1,
foo : 3,
bar : 2,
})
// => `result` is equal to `expected`
Try this R.evolve example in Rambda REPL
evolve<E extends Evolver>(rules: E): <V extends Evolvable<E>>(obj: V) => Evolve<V, E>;
import { mapObject } from './mapObject.js'
import { type } from './type.js'
export function evolveFn(rules, obj) {
return mapObject((x, prop) => {
if (type(x) === 'Object') {
const typeRule = type(rules[prop])
if (typeRule === 'Function') {
return rules[prop](x)
}
if (typeRule === 'Object') {
return evolveFn(rules[prop], x)
}
return x
}
if (type(rules[prop]) === 'Function') {
return rules[prop](x)
}
return x
})(obj)
}
export function evolve(rules) {
return obj => evolveFn(rules, obj)
}
import { evolve } from './evolve.js'
let add = x => y => x + y
test('happy', () => {
const rules = {
foo: add(1),
nested: { bar: x => Object.keys(x).length },
}
const input = {
a: 1,
foo: 2,
nested: { bar: { z: 3 } },
}
const result = evolve(rules)(input)
expect(result).toEqual({
a: 1,
foo: 3,
nested: { bar: 1 },
})
})
test('nested rule is wrong', () => {
const rules = {
foo: add(1),
nested: { bar: 10 },
}
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 } },
})
})
test('is recursive', () => {
const rules = {
nested: {
second: add(-1),
third: add(1),
},
}
const object = {
first: 1,
nested: {
second: 2,
third: 3,
},
}
const expected = {
first: 1,
nested: {
second: 1,
third: 4,
},
}
const result = evolve(rules)(object)
expect(result).toEqual(expected)
})
test('ignores primitive values', () => {
const rules = {
n: 2,
m: 'foo',
}
const object = {
n: 0,
m: 1,
}
const expected = {
n: 0,
m: 1,
}
const result = evolve(rules)(object)
expect(result).toEqual(expected)
})
import { add, evolve, pipe } from 'rambda'
it('R.evolve', () => {
const input = {
foo: 2,
nested: {
a: 1,
bar: 3,
},
}
const rules = {
foo: add(1),
nested: {
a: add(-1),
bar: add(1),
},
}
const result = pipe(
input,
evolve(rules)
)
result.nested.a // $ExpectType number
result.nested.bar // $ExpectType number
result.foo // $ExpectType number
})
excludes
Opposite of R.includes
R.equals
is used to determine equality.
const result = [
R.excludes('ar')('foo'),
R.excludes({a: 2})([{a: 1}])
]
// => [true, true ]
Try this R.excludes example in Rambda REPL
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
filter<T, S extends T>(
predicate: (value: T) => value is S,
): (list: T[]) => S[];
filter<T>(
predicate: BooleanConstructor,
): (list: readonly T[]) => NonNullable<T>[];
filter<T>(
predicate: BooleanConstructor,
): (list: T[]) => NonNullable<T>[];
filter<T>(
predicate: (value: T) => boolean,
): (list: T[]) => T[];
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
}
}
import { filter } from './filter.js'
test('happy', () => {
const isEven = n => n % 2 === 0
expect(filter(isEven)([1, 2, 3, 4])).toEqual([2, 4])
})
import { filter, map, pipe } 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('narrowing type', () => {
interface Foo {
a: number
}
interface Bar extends Foo {
b: string
}
const testList = [{ a: 1 }, { a: 2 }, { a: 3 }]
const filterBar = (x: unknown): x is Bar => {
return typeof (x as Bar).b === 'string'
}
const result = pipe(
testList,
map((x, i) => {
return { a: x.a, b: `${i}` }
}),
filter(filterBar),
)
result // $ExpectType Bar[]
})
it('narrowing type - readonly', () => {
interface Foo {
a: number
}
interface Bar extends Foo {
b: string
}
const testList = [{ a: 1 }, { a: 2 }, { a: 3 }] as const
const filterBar = (x: unknown): x is Bar => {
return typeof (x as Bar).b === 'string'
}
const result = pipe(
testList,
map((x, i) => {
return { a: x.a, b: `${i}` }
}),
filter(filterBar),
)
result // $ExpectType Bar[]
})
it('filtering NonNullable', () => {
const testList = [1, 2, null, undefined, 3]
const result = pipe(testList, filter(Boolean))
result // $ExpectType number[]
})
it('filtering NonNullable - readonly', () => {
const testList = [1, 2, null, undefined, 3] as const
const result = pipe(testList, filter(Boolean))
result // $ExpectType NonNullable<1 | 2 | 3 | null | undefined>[]
// @ts-expect-error
result.includes(null)
})
})
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
filterObject<T extends object>(
valueMapper: (
value: EnumerableStringKeyedValueOf<T>,
key: EnumerableStringKeyOf<T>,
data: T,
) => boolean,
): <U extends T>(data: T) => U;
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
}
}
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
find<T>(predicate: (x: T) => boolean): (list: T[]) => T | undefined;
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++
}
}
}
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('with curry', () => {
const fn = propEq(4, 'a')
expect(find(fn)(list)).toBeUndefined()
})
test('with empty list', () => {
expect(find(() => true)([])).toBeUndefined()
})
import { find, pipe } from 'rambda'
const list = [1, 2, 3]
describe('R.find', () => {
it('happy', () => {
const predicate = (x: number) => x > 2
let 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
findIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
export function findIndex(predicate) {
return list => {
const len = list.length
let index = -1
while (++index < len) {
if (predicate(list[index])) {
return index
}
}
return -1
}
}
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)
})
import { findIndex, pipe } from 'rambda'
const list = [1, 2, 3]
it('R.findIndex', () => {
let 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
findLast<T>(fn: (x: T) => boolean): (list: T[]) => T | undefined;
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
findLastIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
export function findLastIndex(fn) {
return list => {
let index = list.length
while (--index >= 0) {
if (fn(list[index])) {
return index
}
}
return -1
}
}
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)
})
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
})
})
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
flatMap<T, U extends unknown>(transformFn: (x: T extends any[] ? T[number]: never) => U): (listOfLists: T[]) => U[];
export function flatMap(
10 months ago
5 months ago
6 months ago
7 months ago
3 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago