README
Rambda
Rambda
is smaller and faster alternative to the popular functional programming library Ramda. - Documentation
❯ Example use
import { compose, map, filter } from 'rambda'
const result = compose(
map(x => x * 2),
filter(x => x > 2)
)([1, 2, 3, 4])
// => [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.
Important - Rambda version 7.0.0
(or higher) requires Typescript version 4.2.2
(or higher).
Alternative TS definitions are available as
rambda/immutable
. These are Rambda definitions linted with ESLintfunctional/prefer-readonly-type
plugin.
Smaller size
The size of a library affects not only the build bundle size but also the dev bundle size and build time. This is important advantage, expecially for big projects.
Tree-shaking
Currently Rambda is more tree-shakable than Ramda - proven in the following repo.
The repo holds two Angular9
applications: one with small example code of Ramda and the other - same code but with Rambda as import library.
The test shows that Rambda bundle size is 2 MB less than its Ramda counterpart.
There is also Webpack/Rollup/Parcel/Esbuild tree-shaking example including several libraries including Ramda
, Rambda
and Rambdax
.
actually tree-shaking is the initial reason for creation of
Rambda
R.path
, R.paths
, R.assocPath
and R.lensPath
Dot notation for 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} })
R.pick
and R.omit
Comma notation for 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
Most of the valid issues are fixed within 2-3 days.
Closing the issue is usually accompanied by publishing a new patch version of Rambda
to NPM.
❯ Missing Ramda methods
Click to see the full list of 90 Ramda methods not implemented in Rambda
- __
- addIndex
- ap
- aperture
- applyTo
- ascend
- binary
- call
- collectBy
- comparator
- composeWith
- construct
- constructN
- count
- countBy
- descend
- differenceWith
- dissocPath
- empty
- eqBy
- forEachObjIndexed
- gt
- gte
- hasIn
- innerJoin
- insert
- insertAll
- into
- invert
- invertObj
- invoker
- juxt
- keysIn
- lift
- liftN
- lt
- lte
- mapAccum
- mapAccumRight
- memoizeWith
- mergeDeepLeft
- mergeDeepWith
- mergeDeepWithKey
- mergeRight
- mergeWith
- mergeWithKey
- modify
- modifyPath
- nAry
- partialObject
- nthArg
- o
- on
- otherwise
- pair
- partialRight
- pathSatisfies
- pickBy
- pipeWith
- project
- promap
- propSatisfies
- reduceBy
- reduceRight
- reduceWhile
- reduced
- remove
- scan
- sequence
- sortWith
- splitWhenever
- symmetricDifferenceWith
- andThen
- toPairsIn
- transduce
- traverse
- unary
- uncurryN
- unfold
- unionWith
- uniqBy
- unnest
- until
- unwind
- useWith
- valuesIn
- whereAny
- xprod
- thunkify
- default
❯ 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 {compose, add} from 'https://raw.githubusercontent.com/selfrefactor/rambda/master/dist/rambda.esm.js'
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.
If you need more Ramda methods in Rambda, you may either submit a
PR
or check the extended version of Rambda - Rambdax. In case of the former, you may want to consult with Rambda contribution guidelines.
❯ Benchmarks
Click to expand all benchmark results
There are methods which are benchmarked only with Ramda
and Rambda
(i.e. no Lodash
).
Note that some of these methods, are called with and without curring. This is done in order to give more detailed performance feedback.
The benchmarks results are produced from latest versions of Rambda, Lodash(4.17.21) and Ramda(0.27.1).
Ramda
and Rambda
(i.e. no Lodash
).method | Rambda | Ramda | Lodash |
---|---|---|---|
add | 🚀 Fastest | 21.52% slower | 82.15% slower |
adjust | 8.48% slower | 🚀 Fastest | 🔳 |
all | 🚀 Fastest | 1.81% slower | 🔳 |
allPass | 🚀 Fastest | 91.09% slower | 🔳 |
allPass | 🚀 Fastest | 98.56% slower | 🔳 |
and | 🚀 Fastest | 89.09% slower | 🔳 |
any | 🚀 Fastest | 92.87% slower | 45.82% slower |
anyPass | 🚀 Fastest | 98.25% slower | 🔳 |
append | 🚀 Fastest | 2.07% slower | 🔳 |
applySpec | 🚀 Fastest | 80.43% slower | 🔳 |
assoc | 72.32% slower | 60.08% slower | 🚀 Fastest |
clone | 🚀 Fastest | 91.86% slower | 86.48% slower |
compose | 🚀 Fastest | 32.45% slower | 13.68% slower |
converge | 78.63% slower | 🚀 Fastest | 🔳 |
curry | 🚀 Fastest | 28.86% slower | 🔳 |
curryN | 🚀 Fastest | 41.05% slower | 🔳 |
defaultTo | 🚀 Fastest | 48.91% slower | 🔳 |
drop | 🚀 Fastest | 82.35% slower | 🔳 |
dropLast | 🚀 Fastest | 86.74% slower | 🔳 |
equals | 58.37% slower | 96.73% slower | 🚀 Fastest |
filter | 6.7% slower | 72.03% slower | 🚀 Fastest |
find | 🚀 Fastest | 85.14% slower | 42.65% slower |
findIndex | 🚀 Fastest | 86.48% slower | 72.27% slower |
flatten | 🚀 Fastest | 95.26% slower | 10.27% slower |
ifElse | 🚀 Fastest | 58.56% slower | 🔳 |
includes | 🚀 Fastest | 84.63% slower | 🔳 |
indexOf | 🚀 Fastest | 76.63% slower | 🔳 |
indexOf | 🚀 Fastest | 82.2% slower | 🔳 |
init | 🚀 Fastest | 92.24% slower | 13.3% slower |
is | 🚀 Fastest | 57.69% slower | 🔳 |
isEmpty | 🚀 Fastest | 97.14% slower | 54.99% slower |
last | 🚀 Fastest | 93.43% slower | 5.28% slower |
lastIndexOf | 🚀 Fastest | 85.19% slower | 🔳 |
map | 🚀 Fastest | 86.6% slower | 11.73% slower |
match | 🚀 Fastest | 44.83% slower | 🔳 |
merge | 🚀 Fastest | 12.21% slower | 55.76% slower |
none | 🚀 Fastest | 96.48% slower | 🔳 |
objOf | 🚀 Fastest | 38.05% slower | 🔳 |
omit | 🚀 Fastest | 69.95% slower | 97.34% slower |
over | 🚀 Fastest | 56.23% slower | 🔳 |
path | 37.81% slower | 77.81% slower | 🚀 Fastest |
pick | 🚀 Fastest | 19.07% slower | 80.2% slower |
pipe | 0.87% slower | 🚀 Fastest | 🔳 |
prop | 🚀 Fastest | 87.95% slower | 🔳 |
propEq | 🚀 Fastest | 91.92% slower | 🔳 |
range | 🚀 Fastest | 61.8% slower | 57.44% slower |
reduce | 60.48% slower | 77.1% slower | 🚀 Fastest |
repeat | 48.57% slower | 68.98% slower | 🚀 Fastest |
replace | 33.45% slower | 33.99% slower | 🚀 Fastest |
set | 🚀 Fastest | 50.35% slower | 🔳 |
sort | 🚀 Fastest | 40.23% slower | 🔳 |
sortBy | 🚀 Fastest | 25.29% slower | 56.88% slower |
split | 🚀 Fastest | 55.37% slower | 17.64% slower |
splitEvery | 🚀 Fastest | 71.98% slower | 🔳 |
take | 🚀 Fastest | 91.96% slower | 4.72% slower |
takeLast | 🚀 Fastest | 93.39% slower | 19.22% slower |
test | 🚀 Fastest | 82.34% slower | 🔳 |
type | 🚀 Fastest | 48.6% slower | 🔳 |
uniq | 🚀 Fastest | 90.24% slower | 🔳 |
uniqWith | 25.38% slower | 🚀 Fastest | 🔳 |
uniqWith | 14.23% slower | 🚀 Fastest | 🔳 |
update | 🚀 Fastest | 52.35% slower | 🔳 |
view | 🚀 Fastest | 76.15% slower | 🔳 |
❯ Used by
Walmart Canada reported by w-b-dev
API
add
add(a: number, b: number): number
It adds a
and b
.
All Typescript definitions
add(a: number, b: number): number;
add(a: number): (b: number) => number;
R.add source
export function add(a, b) {
if (arguments.length === 1) return _b => add(a, _b)
return Number(a) + Number(b)
}
Tests
import {add} from './add'
import {add as addRamda} from 'ramda'
import {compareCombinations} from './_internals/testUtils'
test('with number', () => {
expect(add(2, 3)).toEqual(5)
expect(add(7)(10)).toEqual(17)
})
test('string is bad input', () => {
expect(add('foo', 'bar')).toBeNaN()
})
test('ramda specs', () => {
expect(add('1', '2')).toEqual(3)
expect(add(1, '2')).toEqual(3)
expect(add(true, false)).toEqual(1)
expect(add(null, null)).toEqual(0)
expect(add(undefined, undefined)).toEqual(NaN)
expect(add(new Date(1), new Date(2))).toEqual(3)
})
const possibleInputs = [
/foo/,
'foo',
true,
3,
NaN,
4,
[],
Promise.resolve(1),
]
describe('brute force', () => {
compareCombinations({
fn: add,
fnRamda: addRamda,
firstInput: possibleInputs,
secondInput: possibleInputs,
callback: errorsCounters => {
expect(errorsCounters).toMatchInlineSnapshot(`
Object {
"ERRORS_MESSAGE_MISMATCH": 0,
"ERRORS_TYPE_MISMATCH": 0,
"RESULTS_MISMATCH": 0,
"SHOULD_NOT_THROW": 0,
"SHOULD_THROW": 0,
"TOTAL_TESTS": 64,
}
`)
},
})
})
Typescript test
import {add} from 'rambda'
describe('R.add', () => {
it('happy', () => {
const result = add(4, 1)
result // $ExpectType number
})
it('curried', () => {
const result = add(4)(1)
result // $ExpectType number
})
})
Rambda is fastest. Ramda is 21.52% slower and Lodash is 82.15% slower
const R = require('../../dist/rambda.js')
const add = [
{
label: 'Rambda',
fn: () => {
R.add(1, 1)
},
},
{
label: 'Ramda',
fn: () => {
Ramda.add(1, 1)
},
},
{
label: 'Lodash',
fn: () => {
_.add(1, 1)
},
},
]
adjust
adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[]
It replaces index
in array list
with the result of replaceFn(list[i])
.
All Typescript definitions
adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[];
adjust<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
R.adjust source
import {curry} from './curry'
import {cloneList} from './_internals/cloneList'
function adjustFn(index, replaceFn, list) {
const actualIndex = index < 0 ? list.length + index : index
if (index >= list.length || actualIndex < 0) return list
const clone = cloneList(list)
clone[actualIndex] = replaceFn(clone[actualIndex])
return clone
}
export const adjust = curry(adjustFn)
Tests
import {add} from './add'
import {adjust} from './adjust'
import {pipe} from './pipe'
const list = [0, 1, 2]
const expected = [0, 11, 2]
test('happy', () => {})
test('happy', () => {
expect(adjust(1, add(10), list)).toEqual(expected)
})
test('with curring type 1 1 1', () => {
expect(adjust(1)(add(10))(list)).toEqual(expected)
})
test('with curring type 1 2', () => {
expect(adjust(1)(add(10), list)).toEqual(expected)
})
test('with curring type 2 1', () => {
expect(adjust(1, add(10))(list)).toEqual(expected)
})
test('with negative index', () => {
expect(adjust(-2, add(10), list)).toEqual(expected)
})
test('when index is out of bounds', () => {
const list = [0, 1, 2, 3]
expect(adjust(4, add(1), list)).toEqual(list)
expect(adjust(-5, add(1), list)).toEqual(list)
})
Rambda is slower than Ramda with 8.48%
const R = require('../../dist/rambda.js')
const list = [0, 1, 2]
const fn = x => x + 1
const index = 1
const adjust = [
{
label: 'Rambda',
fn: () => {
R.adjust(index, fn, list)
R.adjust(index, fn)(list)
},
},
{
label: 'Ramda',
fn: () => {
Ramda.adjust(index, fn, list)
Ramda.adjust(index, fn)(list)
},
},
]
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.
All Typescript definitions
all<T>(predicate: (x: T) => boolean, list: T[]): boolean;
all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.all source
export function all(predicate, list) {
if (arguments.length === 1) return _list => all(predicate, _list)
for (let i = 0; i < list.length; i++) {
if (!predicate(list[i])) return false
}
return true
}
Tests
import {all} from './all'
const list = [0, 1, 2, 3, 4]
test('when true', () => {
const fn = x => x > -1
expect(all(fn)(list)).toBeTrue()
})
test('when false', () => {
const fn = x => x > 2
expect(all(fn, list)).toBeFalse()
})
Typescript test
import {all} from 'rambda'
describe('all', () => {
it('happy', () => {
const result = all(
x => {
x // $ExpectType number
return x > 0
},
[1, 2, 3]
)
result // $ExpectType boolean
})
it('curried needs a type', () => {
const result = all<number>(x => {
x // $ExpectType number
return x > 0
})([1, 2, 3])
result // $ExpectType boolean
})
})
Rambda is faster than Ramda with 1.81%
const R = require('../../dist/rambda.js')
const {
uniqListOfObjects,
uniqListOfStrings,
rangeOfNumbers,
uniqListOfLists,
} = require('./_utils.js')
const limit = 100
const modes = [
[uniqListOfObjects(limit), x => Object.keys(x).length > 2],
[uniqListOfStrings(limit), x => x.length > 0],
[uniqListOfLists(limit), x => x.length > 0],
[rangeOfNumbers(limit), x => x > -1],
]
const applyBenchmark = (fn, input) => {
return fn(input[1], input[0])
}
const tests = [
{
label: 'Rambda',
fn: R.all,
},
{
label: 'Ramda',
fn: Ramda.all,
},
]
allPass
allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean
It returns true
, if all functions of predicates
return true
, when input
is their argument.
All Typescript definitions
allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
R.allPass source
export function allPass(predicates) {
return (...input) => {
let counter = 0
while (counter < predicates.length) {
if (!predicates[counter](...input)) {
return false
}
counter++
}
return true
}
}
Tests
import {allPass} from './allPass'
test('happy', () => {
const rules = [x => typeof x === 'number', x => x > 10, x => x * 7 < 100]
expect(allPass(rules)(11)).toBeTrue()
expect(allPass(rules)(undefined)).toBeFalse()
})
test('when returns true', () => {
const conditionArr = [val => val.a === 1, val => val.b === 2]
expect(
allPass(conditionArr)({
a: 1,
b: 2,
})
).toBeTrue()
})
test('when returns false', () => {
const conditionArr = [val => val.a === 1, val => val.b === 3]
expect(
allPass(conditionArr)({
a: 1,
b: 2,
})
).toBeFalse()
})
test('works with multiple inputs', () => {
var fn = function (w, x, y, z) {
return w + x === y + z
}
expect(allPass([fn])(3, 3, 3, 3)).toBeTrue()
})
Typescript test
import {allPass} from 'rambda'
describe('allPass', () => {
it('happy', () => {
const x = allPass<number>([
y => {
y // $ExpectType number
return typeof y === 'number'
},
y => {
return y > 0
},
])(11)
x // $ExpectType boolean
})
})
Rambda is faster than Ramda with 91.09%
const R = require('../../dist/rambda.js')
const {random} = require('rambdax')
const limit = 100
const min = 10
const max = 1200
function createListOfFunctions(fn, fnLimit) {
return Array(fnLimit)
.fill(null)
.map(() => fn())
}
const modes = [
[
{foo: 1500},
createListOfFunctions(
() => x => Number(x.foo) > random(min, max),
limit
),
],
[
'1500',
createListOfFunctions(() => x => Number(x) > random(min, max), limit),
],
[
[1, 2, 1500],
createListOfFunctions(() => x => x[2] > random(min, max), limit),
],
[1500, createListOfFunctions(() => x => x > random(min, max), limit)],
]
const applyBenchmark = (fn, input) => {
return fn(input[1])(input[0])
}
const tests = [
{
label: 'Rambda',
fn: R.allPass,
},
{
label: 'Ramda',
fn: Ramda.allPass,
},
]
always
always<T>(x: T): (...args: unknown[]) => T
It returns function that always returns x
.
All Typescript definitions
always<T>(x: T): (...args: unknown[]) => T;
R.always source
export function always(x) {
return () => x
}
Tests
import {always} from './always'
import {F} from './F'
test('happy', () => {
const fn = always(7)
expect(fn()).toEqual(7)
expect(fn()).toEqual(7)
})
test('f', () => {
const fn = always(F())
expect(fn()).toBeFalse()
expect(fn()).toBeFalse()
})
Typescript test
import {always} from 'rambda'
describe('R.always', () => {
it('happy', () => {
const fn = always('foo')
fn // $ExpectType (...args: unknown[]) => string
const result = fn()
result // $ExpectType string
})
})
and
Logical AND
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.
All Typescript definitions
any<T>(predicate: (x: T) => boolean, list: T[]): boolean;
any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
R.any source
export function any(predicate, list) {
if (arguments.length === 1) return _list => any(predicate, _list)
let counter = 0
while (counter < list.length) {
if (predicate(list[counter], counter)) {
return true
}
counter++
}
return false
}
Tests
import {any} from './any'
const list = [1, 2, 3]
test('happy', () => {
expect(any(x => x < 0, list)).toBeFalse()
})
test('with curry', () => {
expect(any(x => x > 2)(list)).toBeTrue()
})
Typescript test
import {any} from 'rambda'
describe('R.any', () => {
it('happy', () => {
const result = any(
x => {
x // $ExpectType number
return x > 2
},
[1, 2, 3]
)
result // $ExpectType boolean
})
it('when curried needs a type', () => {
const result = any<number>(x => {
x // $ExpectType number
return x > 2
})([1, 2, 3])
result // $ExpectType boolean
})
})
Rambda is fastest. Ramda is 92.87% slower and Lodash is 45.82% slower
const R = require('../../dist/rambda.js')
const input = [1, 2, 3, 4]
const fn = val => val > 2
const any = [
{
label: 'Rambda',
fn: () => {
R.any(fn, input)
},
},
{
label: 'Ramda',
fn: () => {
Ramda.any(fn, input)
},
},
{
label: 'Lodash.some',
fn: () => {
_.some(input, fn)
},
},
]
anyPass
anyPass<T>(predicates: SafePred<T>[]): SafePred<T>
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
.
All Typescript definitions
anyPass<T>(predicates: SafePred<T>[]): SafePred<T>;
R.anyPass source
export function anyPass(predicates) {
return (...input) => {
let counter = 0
while (counter < predicates.length) {
if (predicates[counter](...input)) {
return true
}
counter++
}
return false
}
}
Tests
import {anyPass} from './anyPass'
test('happy', () => {
const rules = [x => typeof x === 'string', x => x > 10]
const predicate = anyPass(rules)
expect(predicate('foo')).toBeTrue()
expect(predicate(6)).toBeFalse()
})
test('happy', () => {
const rules = [x => typeof x === 'string', x => x > 10]
expect(anyPass(rules)(11)).toBeTrue()
expect(anyPass(rules)(undefined)).toBeFalse()
})
const obj = {
a: 1,
b: 2,
}
test('when returns true', () => {
const conditionArr = [val => val.a === 1, val => val.a === 2]
expect(anyPass(conditionArr)(obj)).toBeTrue()
})
test('when returns false + curry', () => {
const conditionArr = [val => val.a === 2, val => val.b === 3]
expect(anyPass(conditionArr)(obj)).toBeFalse()
})
test('with empty predicates list', () => {
expect(anyPass([])(3)).toEqual(false)
})
test('works with multiple inputs', () => {
var fn = function (w, x, y, z) {
console.log(w, x, y, z)
return w + x === y + z
}
expect(anyPass([fn])(3, 3, 3, 3)).toBeTrue()
})
Typescript test
import {anyPass} from 'rambda'
describe('anyPass', () => {
it('happy', () => {
const x = anyPass<number>([
y => {
y // $ExpectType number
return typeof y === 'number'
},
y => {
return y > 0
},
])(11)
x // $ExpectType boolean
})
})
Rambda is faster than Ramda with 98.25%
const R = require('../../dist/rambda.js')
const rules = [x => typeof x === 'boolean', x => x > 20, x => x * 7 < 100]
const anyPass = [
{
label: 'Rambda',
fn: () => {
R.anyPass(rules)(11)
},
},
{
label: 'Ramda',
fn: () => {
Ramda.anyPass(rules)(11)
},
},
]
append
append<T>(x: T, list: T[]): T[]
It adds element x
at the end of list
.
All Typescript definitions
append<T>(x: T, list: T[]): T[];
append<T>(x: T): <T>(list: T[]) => T[];
R.append source
import {cloneList} from './_internals/cloneList'
export function append(x, input) {
if (arguments.length === 1) return _input => append(x, _input)
if (typeof input === 'string') return input.split('').concat(x)
const clone = cloneList(input)
clone.push(x)
return clone
}
Tests
import {append} from './append'
test('happy', () => {
expect(append('tests', ['write', 'more'])).toEqual([
'write',
'more',
'tests',
])
})
test('append to empty array', () => {
expect(append('tests')([])).toEqual(['tests'])
})
test('with strings', () => {
expect(append('o', 'fo')).toEqual(['f', 'o', 'o'])
})
Typescript test
import {append} from 'rambda'
const list = [1, 2, 3]
describe('R.append', () => {
it('happy', () => {
const result = append(4, list)
result // $ExpectType number[]
})
it('curried', () => {
const result = append(4)(list)
result // $ExpectType number[]
})
})
Rambda is faster than Ramda with 2.07%
const R = require('../../dist/rambda.js')
const append = [
{
label: 'Rambda',
fn: () => {
R.append(0)([1, 2, 3, 4])
R.append('bar')('foo')
},
},
{
label: 'Ramda',
fn: () => {
Ramda.append(0)([1, 2, 3, 4])
Ramda.append('bar')('foo')
},
},
]
apply
apply<T = any>(fn: (...args: any[]) => T, args: any[]): T
It applies function fn
to the list of arguments.
This is useful for creating a fixed-arity function from a variadic function. fn
should be a bound function if context is significant.
All Typescript definitions
apply<T = any>(fn: (...args: any[]) => T, args: any[]): T;
apply<T = any>(fn: (...args: any[]) => T): (args: any[]) => T;
R.apply source
export function apply(fn, args) {
if (arguments.length === 1) {
return _args => apply(fn, _args)
}
return fn.apply(this, args)
}
Tests
import {apply} from './apply'
import {bind} from './bind'
import {identity} from './identity'
test('happy', () => {
expect(apply(identity, [1, 2, 3])).toEqual(1)
})
test('applies function to argument list', function () {
expect(apply(Math.max, [1, 2, 3, -99, 42, 6, 7])).toEqual(42)
})
test('provides no way to specify context', function () {
const obj = {
method: function () {
return this === obj
},
}
expect(apply(obj.method, [])).toEqual(false)
expect(apply(bind(obj.method, obj), [])).toEqual(true)
})
Typescript test
import {apply, identity} from 'rambda'
describe('R.apply', () => {
it('happy', () => {
const result = apply<number>(identity, [1, 2, 3])
result // $ExpectType number
})
it('curried', () => {
const fn = apply<number>(identity)
const result = fn([1, 2, 3])
result // $ExpectType number
})
})
applySpec
applySpec<Spec extends Record<string, (...args: any[]) => any>>(
spec: Spec
): (
...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
All Typescript definitions
applySpec<Spec extends Record<string, (...args: any[]) => any>>(
spec: Spec
): (
...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> };
applySpec<T>(spec: any): (...args: any[]) => T;
R.applySpec source
import {_isArray} from './_internals/_isArray'
// recursively traverse the given spec object to find the highest arity function
function __findHighestArity(spec, max = 0) {
for (const key in spec) {
if (spec.hasOwnProperty(key) === false || key === 'constructor') continue
if (typeof spec[key] === 'object') {
max = Math.max(max, __findHighestArity(spec[key]))
}
if (typeof spec[key] === 'function') {
max = Math.max(max, spec[key].length)
}
}
return max
}
function __filterUndefined() {
const defined = []
let i = 0
const l = arguments.length
while (i < l) {
if (typeof arguments[i] === 'undefined') break
defined[i] = arguments[i]
i++
}
return defined
}
function __applySpecWithArity(spec, arity, cache) {
const remaining = arity - cache.length
if (remaining === 1)
return x =>
__applySpecWithArity(spec, arity, __filterUndefined(...cache, x))
if (remaining === 2)
return (x, y) =>
__applySpecWithArity(spec, arity, __filterUndefined(...cache, x, y))
if (remaining === 3)
return (x, y, z) =>
__applySpecWithArity(spec, arity, __filterUndefined(...cache, x, y, z))
if (remaining === 4)
return (x, y, z, a) =>
__applySpecWithArity(
spec,
arity,
__filterUndefined(...cache, x, y, z, a)
)
if (remaining > 4)
return (...args) =>
__applySpecWithArity(spec, arity, __filterUndefined(...cache, ...args))
// handle spec as Array
if (_isArray(spec)) {
const ret = []
let i = 0
const l = spec.length
for (; i < l; i++) {
// handle recursive spec inside array
if (typeof spec[i] === 'object' || _isArray(spec[i])) {
ret[i] = __applySpecWithArity(spec[i], arity, cache)
}
// apply spec to the key
if (typeof spec[i] === 'function') {
ret[i] = spec[i](...cache)
}
}
return ret
}
// handle spec as Object
const ret = {}
// apply callbacks to each property in the spec object
for (const key in spec) {
if (spec.hasOwnProperty(key) === false || key === 'constructor') continue
// apply the spec recursively
if (typeof spec[key] === 'object') {
ret[key] = __applySpecWithArity(spec[key], arity, cache)
continue
}
// apply spec to the key
if (typeof spec[key] === 'function') {
ret[key] = spec[key](...cache)
}
}
return ret
}
export function applySpec(spec, ...args) {
// get the highest arity spec function, cache the result and pass to __applySpecWithArity
const arity = __findHighestArity(spec)
if (arity === 0) {
return () => ({})
}
const toReturn = __applySpecWithArity(spec, arity, args)
return toReturn
}
Tests
import {applySpec as applySpecRamda, nAry} from 'ramda'
import {add, always, compose, dec, inc, map, path, prop, T} from '../rambda'
import {applySpec} from './applySpec'
test('different than Ramda when bad spec', () => {
const result = applySpec({sum: {a: 1}})(1, 2)
const ramdaResult = applySpecRamda({sum: {a: 1}})(1, 2)
expect(result).toEqual({})
expect(ramdaResult).toEqual({sum: {a: {}}})
})
test('works with empty spec', () => {
expect(applySpec({})()).toEqual({})
expect(applySpec([])(1, 2)).toEqual({})
expect(applySpec(null)(1, 2)).toEqual({})
})
test('works with unary functions', () => {
const result = applySpec({
v: inc,
u: dec,
})(1)
const expected = {
v: 2,
u: 0,
}
expect(result).toEqual(expected)
})
test('works with binary functions', () => {
const result = applySpec({sum: add})(1, 2)
expect(result).toEqual({sum: 3})
})
test('works with nested specs', () => {
const result = applySpec({
unnested: always(0),
nested: {sum: add},
})(1, 2)
const expected = {
unnested: 0,
nested: {sum: 3},
}
expect(result).toEqual(expected)
})
test('works with arrays of nested specs', () => {
const result = applySpec({
unnested: always(0),
nested: [{sum: add}],
})(1, 2)
expect(result).toEqual({
unnested: 0,
nested: [{sum: 3}],
})
})
test('works with arrays of spec objects', () => {
const result = applySpec([{sum: add}])(1, 2)
expect(result).toEqual([{sum: 3}])
})
test('works with arrays of functions', () => {
const result = applySpec([map(prop('a')), map(prop('b'))])([
{
a: 'a1',
b: 'b1',
},
{
a: 'a2',
b: 'b2',
},
])
const expected = [
['a1', 'a2'],
['b1', 'b2'],
]
expect(result).toEqual(expected)
})
test('works with a spec defining a map key', () => {
expect(applySpec({map: prop('a')})({a: 1})).toEqual({map: 1})
})
test('cannot retains the highest arity', () => {
const f = applySpec({
f1: nAry(2, T),
f2: nAry(5, T),
})
const fRamda = applySpecRamda({
f1: nAry(2, T),
f2: nAry(5, T),
})
expect(f.length).toBe(0)
expect(fRamda.length).toBe(5)
})
test('returns a curried function', () => {
expect(applySpec({sum: add})(1)(2)).toEqual({sum: 3})
})
// Additional tests
// ============================================
test('arity', () => {
const spec = {
one: x1 => x1,
two: (x1, x2) => x1 + x2,
three: (x1, x2, x3) => x1 + x2 + x3,
}
expect(applySpec(spec, 1, 2, 3)).toEqual({
one: 1,
two: 3,
three: 6,
})
})
test('arity over 5 arguments', () => {
const spec = {
one: x1 => x1,
two: (x1, x2) => x1 + x2,
three: (x1, x2, x3) => x1 + x2 + x3,
four: (x1, x2, x3, x4) => x1 + x2 + x3 + x4,
five: (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5,
}
expect(applySpec(spec, 1, 2, 3, 4, 5)).toEqual({
one: 1,
two: 3,
three: 6,
four: 10,
five: 15,
})
})
test('curried', () => {
const spec = {
one: x1 => x1,
two: (x1, x2) => x1 + x2,
three: (x1, x2, x3) => x1 + x2 + x3,
}
expect(applySpec(spec)(1)(2)(3)).toEqual({
one: 1,
two: 3,
three: 6,
})
})
test('curried over 5 arguments', () => {
const spec = {
one: x1 => x1,
two: (x1, x2) => x1 + x2,
three: (x1, x2, x3) => x1 + x2 + x3,
four: (x1, x2, x3, x4) => x1 + x2 + x3 + x4,
five: (x1, x2, x3, x4, x5) => x1 + x2 + x3 + x4 + x5,
}
expect(applySpec(spec)(1)(2)(3)(4)(5)).toEqual({
one: 1,
two: 3,
three: 6,
four: 10,
five: 15,
})
})
test('undefined property', () => {
const spec = {prop: path(['property', 'doesnt', 'exist'])}
expect(applySpec(spec, {})).toEqual({prop: undefined})
})
test('restructure json object', () => {
const spec = {
id: path('user.id'),
name: path('user.firstname'),
profile: path('user.profile'),
doesntExist: path('user.profile.doesntExist'),
info: {views: compose(inc, prop('views'))},
type: always('playa'),
}
const data = {
user: {
id: 1337,
firstname: 'john',
lastname: 'shaft',
profile: 'shaft69',
},
views: 42,
}
expect(applySpec(spec, data)).toEqual({
id: 1337,
name: 'john',
profile: 'shaft69',
doesntExist: undefined,
info: {views: 43},
type: 'playa',
})
})
Typescript test
import {multiply, applySpec, inc, dec, add} from 'rambda'
describe('applySpec', () => {
it('ramda 1', () => {
const result = applySpec({
v: inc,
u: dec,
})(1)
result // $ExpectType { v: number; u: number; }
})
it('ramda 1', () => {
interface Output {
sum: number
multiplied: number
}
const result = applySpec<Output>({
sum: add,
multiplied: multiply,
})(1, 2)
result // $ExpectType Output
})
})
Rambda is faster than Ramda with 80.43%
const R = require('../../dist/rambda.js')
const curryN = [
{
label: 'Rambda',
fn: () => {
const data = {
a: {
b: {c: 1},
d: 2,
},
}
const spec = {
c: R.path(['a', 'b', 'c']),
d: R.path(['a', 'd']),
}
R.applySpec(spec, data)
},
},
{
label: 'Ramda',
fn: () => {
const data = {
a: {
b: {c: 1},
d: 2,
},
}
const spec = {
c: Ramda.path(['a', 'b', 'c']),
d: Ramda.path(['a', 'd']),
}
Ramda.applySpec(spec, data)
},
},
]
assoc
assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & Omit<U, K>
It makes a shallow clone of obj
with setting or overriding the property prop
with newValue
.
All Typescript definitions
assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & Omit<U, K>;
assoc<T, K extends string>(prop: K, val: T): <U>(obj: U) => Record<K, T> & Omit<U, K>;
assoc<K extends string>(prop: K): AssocPartialOne<K>;
R.assoc source
import {curry} from './curry'
function assocFn(prop, newValue, obj) {
return Object.assign({}, obj, {[prop]: newValue})
}
export const assoc = curry(assocFn)
Tests
import {assoc} from './assoc'
test('adds a key to an empty object', () => {
expect(assoc('a', 1, {})).toEqual({a: 1})
})
test('adds a key to a non-empty object', () => {
expect(assoc('b', 2, {a: 1})).toEqual({
a: 1,
b: 2,
})
})
test('adds a key to a non-empty object - curry case 1', () => {
expect(assoc('b', 2)({a: 1})).toEqual({
a: 1,
b: 2,
})
})
test('adds a key to a non-empty object - curry case 2', () => {
expect(assoc('b')(2, {a: 1})).toEqual({
a: 1,
b: 2,
})
})
test('adds a key to a non-empty object - curry case 3', () => {
const result = assoc('b')(2)({a: 1})
expect(result).toEqual({
a: 1,
b: 2,
})
})
test('changes an existing key', () => {
expect(assoc('a', 2, {a: 1})).toEqual({a: 2})
})
test('undefined is considered an empty object', () => {
expect(assoc('a', 1, undefined)).toEqual({a: 1})
})
test('null is considered an empty object', () => {
expect(assoc('a', 1, null)).toEqual({a: 1})
})
test('value can be null', () => {
expect(assoc('a', null, null)).toEqual({a: null})
})
test('value can be undefined', () => {
expect(assoc('a', undefined, null)).toEqual({a: undefined})
})
test('assignment is shallow', () => {
expect(assoc('a', {b: 2}, {a: {c: 3}})).toEqual({a: {b: 2}})
})
Typescript test
import {assoc} from 'rambda'
const obj = {a: 1}
const newValue = 2
const newProp = 'b'
describe('R.assoc', () => {
it('happy', () => {
const result = assoc(newProp, newValue, obj)
result.a // $ExpectType number
result.b // $ExpectType number
})
it('curried 1', () => {
const result = assoc(newProp, newValue)(obj)
result.a // $ExpectType number
result.b // $ExpectType number
})
it('curried 2', () => {
const result = assoc(newProp)(newValue)(obj)
result.a // $ExpectType number
result.b // $ExpectType number
})
})
Lodash is fastest. Rambda is 72.32% slower and Ramda is 60.08% slower
const R = require('../../dist/rambda.js')
const input = {
a: 1,
b: 2,
}
const key = 'c'
const value = 3
const assoc = [
{
label: 'Rambda',
fn: () => {
R.assoc(key, value, input)
},
},
{
label: 'Ramda',
fn: () => {
Ramda.assoc(key, value, input)
},
},
{
label: 'Lodash.set',
fn: () => {
_.set(input, key, value)
},
},
]
assocPath
assocPath<Output>(path: Path, newValue: any, obj: object): Output
It makes a shallow clone of obj
with setting or overriding with newValue
the property found with path
.
All Typescript definitions
assocPath<Output>(path: Path, newValue: any, obj: object): Output;
assocPath<Output>(path: Path, newValue: any): (obj: object) => Output;
assocPath<Output>(path: Path): (newValue: any) => (obj: object) => Output;
R.assocPath source
import {_isArray} from './_internals/_isArray'
import {_isInteger} from './_internals/_isInteger'
import {assoc} from './assoc'
import {curry} from './curry'
import {cloneList} from './_internals/cloneList'
function assocPathFn(path, newValue, input) {
const pathArrValue =
typeof path === 'string'
? path.split('.').map(x => (_isInteger(Number(x)) ? Number(x) : x))
: path
if (pathArrValue.length === 0) {
return newValue
}
const index = pathArrValue[0]
if (pathArrValue.length > 1) {
const condition =
typeof input !== 'object' ||
input === null ||
!input.hasOwnProperty(index)
const nextinput = condition
? _isInteger(pathArrValue[1])
? []
: {}
: input[index]
newValue = assocPathFn(
Array.prototype.slice.call(pathArrValue, 1),
newValue,
nextinput
)
}
if (_isInteger(index) && _isArray(input)) {
const arr = cloneList(input)
arr[index] = newValue
return arr
}
return assoc(index, newValue, input)
}
export const assocPath = curry(assocPathFn)
Tests
import {assocPath} from './assocPath'
test('string can be used as path input', () => {
const testObj = {
a: [{b: 1}, {b: 2}],
d: 3,
}
const result = assocPath('a.0.b', 10, testObj)
const expected = {
a: [{b: 10}, {b: 2}],
d: 3,
}
expect(result).toEqual(expected)
})
test('bug', () => {
/*
https://github.com/selfrefactor/rambda/issues/524
*/
const state = {}
const withDateLike = assocPath(
['outerProp', '2020-03-10'],
{prop: 2},
state
)
const withNumber = assocPath(['outerProp', '5'], {prop: 2}, state)
const withDateLikeExpected = {outerProp: {'2020-03-10': {prop: 2}}}
const withNumberExpected = {outerProp: {5: {prop: 2}}}
expect(withDateLike).toEqual(withDateLikeExpected)
expect(withNumber).toEqual(withNumberExpected)
})
test('adds a key to an empty object', () => {
expect(assocPath(['a'], 1, {})).toEqual({a: 1})
})
test('adds a key to a non-empty object', () => {
expect(assocPath('b', 2, {a: 1})).toEqual({
a: 1,
b: 2,
})
})
test('adds a nested key to a non-empty object', () => {
expect(assocPath('b.c', 2, {a: 1})).toEqual({
a: 1,
b: {c: 2},
})
})
test('adds a nested key to a nested non-empty object - curry case 1', () => {
expect(
assocPath(
'b.d',
3
)({
a: 1,
b: {c: 2},
})
).toEqual({
a: 1,
b: {
c: 2,
d: 3,
},
})
})
test('adds a key to a non-empty object - curry case 1', () => {
expect(assocPath('b', 2)({a: 1})).toEqual({
a: 1,
b: 2,
})
})
test('adds a nested key to a non-empty object - curry case 1', () => {
expect(assocPath('b.c', 2)({a: 1})).toEqual({
a: 1,
b: {c: 2},
})
})
test('adds a key to a non-empty object - curry case 2', () => {
expect(assocPath('b')(2, {a: 1})).toEqual({
a: 1,
b: 2,
})
})
test('adds a key to a non-empty object - curry case 3', () => {
const result = assocPath('b')(2)({a: 1})
expect(result).toEqual({
a: 1,
b: 2,
})
})
test('changes an existing key', () => {
expect(assocPath('a', 2, {a: 1})).toEqual({a: 2})
})
test('undefined is considered an empty object', () => {
expect(assocPath('a', 1, undefined)).toEqual({a: 1})
})
test('null is considered an empty object', () => {
expect(assocPath('a', 1, null)).toEqual({a: 1})
})
test('value can be null', () => {
expect(assocPath('a', null, null)).toEqual({a: null})
})
test('value can be undefined', () => {
expect(assocPath('a', undefined, null)).toEqual({a: undefined})
})
test('assignment is shallow', () => {
expect(assocPath('a', {b: 2}, {a: {c: 3}})).toEqual({a: {b: 2}})
})
test('empty array as path', () => {
const result = assocPath([], 3, {
a: 1,
b: 2,
})
expect(result).toEqual(3)
})
test('happy', () => {
const expected = {foo: {bar: {baz: 42}}}
const result = assocPath(['foo', 'bar', 'baz'], 42, {foo: null})
expect(result).toEqual(expected)
})
Typescript test
import {assocPath} from 'rambda'
interface Output {
a: number
foo: {bar: number}
}
describe('R.assocPath - user must explicitly set type of output', () => {
it('with array as path input', () => {
const result = assocPath<Output>(['foo', 'bar'], 2, {a: 1})
result // $ExpectType Output
})
it('with string as path input', () => {
const result = assocPath<Output>('foo.bar', 2, {a: 1})
result // $ExpectType Output
})
})
describe('R.assocPath - curried', () => {
it('with array as path input', () => {
const result = assocPath<Output>(['foo', 'bar'], 2)({a: 1})
result // $ExpectType Output
})
it('with string as path input', () => {
const result = assocPath<Output>('foo.bar', 2)({a: 1})
result // $ExpectType Output
})
})
bind
bind<F extends (...args: any[]) => any, T>(fn: F, thisObj: T): (...args: Parameters<F>) => ReturnType<F>
Creates a function that is bound to a context.
All Typescript definitions
bind<F extends (...args: any[]) => any, T>(fn: F, thisObj: T): (...args: Parameters<F>) => ReturnType<F>;
bind<F extends (...args: any[]) => any, T>(fn: F): (thisObj: T) => (...args: Parameters<F>) => ReturnType<F>;
R.bind source
import {curryN} from './curryN'
export function bind(fn, thisObj) {
if (arguments.length === 1) {
return _thisObj => bind(fn, _thisObj)
}
return curryN(fn.length, (...args) => fn.apply(thisObj, args))
}
Tests
import {bind} from './bind'
function Foo(x) {
this.x = x
}
function add(x) {
return this.x + x
}
function Bar(x, y) {
this.x = x
this.y = y
}
Bar.prototype = new Foo()
Bar.prototype.getX = function () {
return 'prototype getX'
}
test('returns a function', function () {
expect(typeof bind(add)(Foo)).toEqual('function')
})
test('returns a function bound to the specified context object', function () {
const f = new Foo(12)
function isFoo() {
return this instanceof Foo
}
const isFooBound = bind(isFoo, f)
expect(isFoo()).toEqual(false)
expect(isFooBound()).toEqual(true)
})
test('works with built-in types', function () {
const abc = bind(String.prototype.toLowerCase, 'ABCDEFG')
expect(typeof abc).toEqual('function')
expect(abc()).toEqual('abcdefg')
})
test('works with user-defined types', function () {
const f = new Foo(12)
function getX() {
return this.x
}
const getXFooBound = bind(getX, f)
expect(getXFooBound()).toEqual(12)
})
test('works with plain objects', function () {
const pojso = {
x: 100,
}
function incThis() {
return this.x + 1
}
const incPojso = bind(incThis, pojso)
expect(typeof incPojso).toEqual('function')
expect(incPojso()).toEqual(101)
})
test('does not interfere with existing object methods', function () {
const b = new Bar('a', 'b')
function getX() {
return this.x
}
const getXBarBound = bind(getX, b)
expect(b.getX()).toEqual('prototype getX')
expect(getXBarBound()).toEqual('a')
})
test('preserves arity', function () {
const f0 = function () {
return 0
}
const f1 = function (a) {
return a
}
const f2 = function (a, b) {
return a + b
}
const f3 = function (a, b, c) {
return a + b + c
}
expect(bind(f0, {}).length).toEqual(0)
expect(bind(f1, {}).length).toEqual(1)
expect(bind(f2, {}).length).toEqual(2)
expect(bind(f3, {}).length).toEqual(3)
})
Typescript test
import {bind} from 'rambda'
class Foo {}
function isFoo<T = any>(this: T): boolean {
return this instanceof Foo
}
describe('R.bind', () => {
it('happy', () => {
const foo = new Foo()
const result = bind(isFoo, foo)()
result // $ExpectType boolean
})
})
both
both(pred1: Pred, pred2: Pred): Pred
It returns a function with input
argument.
This function will return true
, if both firstCondition
and secondCondition
return true
when input
is passed as their argument.
All Typescript definitions
both(pred1: Pred, pred2: Pred): Pred;
both<T>(pred1: Predicate<T>, pred2: Predicate<T>): Predicate<T>;
both<T>(pred1: Predicate<T>): (pred2: Predicate<T>) => Predicate<T>;
both(pred1: Pred): (pred2: Pred) => Pred;
R.both source
export function both(f, g) {
if (arguments.length === 1) return _g => both(f, _g)
return (...input) => f(...input) && g(...input)
}
Tests
import {both} from './both'
const firstFn = val => val > 0
const secondFn = val => val < 10
test('with curry', () => {
expect(both(firstFn)(secondFn)(17)).toBeFalse()
})
test('without curry', () => {
expect(both(firstFn, secondFn)(7)).toBeTrue()
})
test('with multiple inputs', () => {
const between = function (a, b, c) {
return a < b && b < c
}
const total20 = function (a, b, c) {
return a + b + c === 20
}
const fn = both(between, total20)
expect(fn(5, 7, 8)).toBeTrue()
})
test('skip evaluation of the second expression', () => {
let effect = 'not evaluated'
const F = function () {
return false
}
const Z = function () {
effect = 'Z got evaluated'
}
both(F, Z)()
expect(effect).toBe('not evaluated')
})
Typescript test
import {both} from 'rambda'
describe('R.both', () => {
it('with passed type', () => {
const fn = both<number>(
x => x > 1,
x => x % 2 === 0
)
fn // $ExpectType Predicate<number>
const result = fn(2) // $ExpectType boolean
result // $ExpectType boolean
})
it('with passed type - curried', () => {
const fn = both<number>(x => x > 1)(x => x % 2 === 0)
fn // $ExpectType Predicate<number>
const result = fn(2)
result // $ExpectType boolean
})
it('no type passed', () => {
const fn = both(
x => {
x // $ExpectType any
return x > 1
},
x => {
x // $ExpectType any
return x % 2 === 0
}
)
const result = fn(2)
result // $ExpectType boolean
})
it('no type passed - curried', () => {
const fn = both((x: number) => {
x // $ExpectType number
return x > 1
})((x: number) => {
x // $ExpectType number
return x % 2 === 0
})
const result = fn(2)
result // $ExpectType boolean
})
})
chain
chain<T, U>(fn: (n: T) => U[], list: T[]): U[]
The method is also known as flatMap
.
All Typescript definitions
chain<T, U>(fn: (n: T) => U[], list: T[]): U[];
chain<T, U>(fn: (n: T) => U[]): (list: T[]) => U[];
R.chain source
export function chain(fn, list) {
if (arguments.length === 1) {
return _list => chain(fn, _list)
}
return [].concat(...list.map(fn))
}
Tests
import {chain} from './chain'
import {chain as chainRamda} from 'ramda'
const duplicate = n => [n, n]
test('happy', () => {
const fn = x => [x * 2]
const list = [1, 2, 3]
const result = chain(fn, list)
expect(result).toEqual([2, 4, 6])
})
test('maps then flattens one level', () => {
expect(chain(duplicate, [1, 2, 3])).toEqual([1, 1, 2, 2, 3, 3])
})
test('maps then flattens one level - curry', () => {
expect(chain(duplicate)([1, 2, 3])).toEqual([1, 1, 2, 2, 3, 3])
})
test('flattens only one level', () => {
const nest = n => [[n]]
expect(chain(nest, [1, 2, 3])).toEqual([[1], [2], [3]])
})
test('can compose', () => {
function dec(x) {
return [x - 1]
}
function times2(x) {
return [x * 2]
}
var mdouble = chain(times2)
var mdec = chain(dec)
expect(mdec(mdouble([10, 20, 30]))).toEqual([19, 39, 59])
})
test('@types/ramda broken test', () => {
const score = {
maths: 90,
physics: 80,
}
const calculateTotal = score => {
const {maths, physics} = score
return maths + physics
}
const assocTotalToScore = (total, score) => ({...score, total})
const calculateAndAssocTotalToScore = chainRamda(
assocTotalToScore,
calculateTotal
)
expect(() => calculateAndAssocTotalToScore(score)).toThrow()
})
Typescript test
import {chain} from 'rambda'
const list = [1, 2, 3]
const fn = (x: number) => [`${x}`, `${x}`]
describe('R.chain', () => {
it('without passing type', () => {
const result = chain(fn, list)
result // $ExpectType string[]
const curriedResult = chain(fn)(list)
curriedResult // $ExpectType string[]
})
})
clamp
Restrict a number input
to be within min
and max
limits.
If input
is bigger than max
, then the result is max
.
If input
is smaller than min
, then the result is min
.
clone
It creates a deep copy of the input
, which may contain (nested) Arrays and Objects, Numbers, Strings, Booleans and Dates.
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)
.
compose
It performs right-to-left function composition.
concat
It returns a new string or array, which is the result of merging x
and y
.
cond
It takes list with conditions
and returns a new function fn
that expects input
as argument.
This function will start evaluating the conditions
in order to find the first winner(order of conditions matter).
The winner is this condition, which left side returns true
when input
is its argument. Then the evaluation of the right side of the winner will be the final result.
If no winner is found, then fn
returns undefined
.
converge
Accepts a converging function and a list of branching functions and returns a new function. When invoked, this new function is applied to some arguments, each branching function is applied to those same arguments. The results of each branching function are passed as arguments to the converging function to produce the return value.
curry
It expects a function as input and returns its curried version.
curryN
It returns a curried equivalent of the provided function, with the specified arity.
dec
It decrements a number.
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).
All Typescript definitions
defaultTo<T>(defaultValue: T, input: T | null | undefined): T;
defaultTo<T>(defaultValue: T): (input: T | null | undefined) => T;
R.defaultTo source
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
}
Tests
import {defaultTo} from './defaultTo'
test('with undefined', () => {
expect(defaultTo('foo')(undefined)).toEqual('foo')
})
test('with null', () => {
expect(defaultTo('foo')(null)).toEqual('foo')
})
test('with NaN', () => {
expect(defaultTo('foo')(NaN)).toEqual('foo')
})
test('with empty string', () => {
expect(defaultTo('foo', '')).toEqual('')
})
test('with false', () => {
expect(defaultTo('foo', false)).toEqual(false)
})
test('when inputArgument passes initial check', () => {
expect(defaultTo('foo', 'bar')).toEqual('bar')
})
Typescript test
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
})
})
Rambda is faster than Ramda with 48.91%
const R = require('../../dist/rambda.js')
const input = [null, undefined, 5]
const defaultTo = [
{
label: 'Rambda',
fn: () => {
R.defaultTo(3, input[0])
},
},
{
label: 'Ramda',
fn: () => {
Ramda.defaultTo(3, input[0])
},
},
{
label: 'Rambda with multiple arguments',
fn: () => {
R.defaultTo(3, ...input)
},
},
]
difference
difference<T>(a: T[], b: T[]): T[]
It returns the uniq set of all elements in the first list a
not contained in the second list b
.
R.equals
is used to determine equality.
All Typescript definitions
difference<T>(a: T[], b: T[]): T[];
difference<T>(a: T[]): (b: T[]) => T[];
R.difference source
import {includes} from './includes'
import {uniq} from './uniq'
export function difference(a, b) {
if (arguments.length === 1) return _b => difference(a, _b)
return uniq(a).filter(aInstance => !includes(aInstance, b))
}
Tests
import {difference} from './difference'
import {difference as differenceRamda} from 'ramda'
test('difference', () => {
const a = [1, 2, 3, 4]
const b = [3, 4, 5, 6]
expect(difference(a)(b)).toEqual([1, 2])
expect(difference([], [])).toEqual([])
})
test('difference with objects', () => {
const a = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]
const b = [{id: 3}, {id: 4}, {id: 5}, {id: 6}]
expect(difference(a, b)).toEqual([{id: 1}, {id: 2}])
})
test('no duplicates in first list', () => {
const M2 = [1, 2, 3, 4, 1, 2, 3, 4]
const N2 = [3, 3, 4, 4, 5, 5, 6, 6]
expect(difference(M2, N2)).toEqual([1, 2])
})
test('should use R.equals', () => {
expect(difference([1], [1]).length).toEqual(0)
expect(differenceRamda([NaN], [NaN]).length).toEqual(0)
})
Typescript test
import {difference} from 'rambda'
const list1 = [1, 2, 3]
const list2 = [1, 2, 4]
describe('R.difference', () => {
it('happy', () => {
const result = difference(list1, list2)
result // $ExpectType number[]
})
it('curried', () => {
const result = difference(list1)(list2)
result // $ExpectType number[]
})
})
</deta