README
Rambdax
Extended version of Rambda(utility library) - Documentation
Rambda
is smaller and faster alternative to the popular functional programming library Ramda. - Documentation
❯ Differences between Rambda and Rambdax
Rambdax passthrough all Rambda methods and introduce some new functions.
The idea of Rambdax is to extend Rambda without worring for Ramda compatibility.
❯ Example use
import { composeAsync, filter, delay, mapAsync } from 'rambdax'
const result = await composeAsync(
mapAsync(async x => {
await delay(100)
return x + 1
}),
filter(x => x > 1)
)([1, 2, 3])
// => [3, 4]
You can test this example in Rambda's REPL
❯ Rambdax'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 - Rambdax version 8.0.0
(or higher) requires Typescript version 4.2.2
(or higher).
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
Extendable with Ramda community projects
Rambdax
implements some methods from Ramda
community projects, such as R.lensSatisfies
, R.lensEq
and R.viewOr
.
Alternative TS definitions
Alternative TS definitions are available as rambdax/immutable
. These are Rambdax definitions linted with ESLint functional/prefer-readonly-type
plugin.
❯ Missing Ramda methods
Click to see the full list of 85 Ramda methods not implemented in Rambda
- __
- addIndex
- ap
- aperture
- applyTo
- ascend
- binary
- call
- comparator
- composeK
- composeP
- composeWith
- construct
- constructN
- contains
- 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
- nAry
- nthArg
- o
- otherwise
- pair
- partialRight
- pathSatisfies
- pickBy
- pipeK
- pipeP
- pipeWith
- project
- propSatisfies
- reduceBy
- reduceRight
- reduceWhile
- reduced
- remove
- scan
- sequence
- sortWith
- symmetricDifferenceWith
- andThen
- toPairsIn
- transduce
- traverse
- unary
- uncurryN
- unfold
- unionWith
- uniqBy
- unnest
- until
- useWith
- valuesIn
- xprod
- thunkify
- default
❯ Install
yarn add rambdax
For UMD usage either use
./dist/rambdax.umd.js
or the following CDN link:
https://unpkg.com/rambdax@CURRENT_VERSION/dist/rambdax.umd.js
- with deno
import {compose, add} from 'https://raw.githubusercontent.com/selfrefactor/rambdax/master/dist/rambdax.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.
❯ 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
.
:boom: It doesn't work with strings, as the inputs are parsed to numbers before calculation.
R.add(2, 3) // => 5
Try this R.add example in Rambda REPL
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,
}
`)
},
})
})
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])
.
R.adjust(
0,
a => a + 1,
[0, 100]
) // => [1, 100]
Try this R.adjust example in Rambda REPL
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)
})
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.all(predicate, list)
// => true
Try this R.all example in Rambda REPL
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()
})
allFalse
allFalse(...inputs: any[]): boolean
It returns true
if all inputs
arguments are falsy(empty objects and empty arrays are considered falsy).
Functions are valid inputs, but these functions cannot have their own arguments.
This method is very similar to R.anyFalse
, R.anyTrue
and R.allTrue
R.allFalse(0, null, [], {}, '', () => false)
// => true
Try this R.allFalse example in Rambda REPL
R.allFalse source
import {isTruthy} from './_internals/isTruthy'
import {type} from './type'
export function allFalse(...inputs) {
let counter = 0
while (counter < inputs.length) {
const x = inputs[counter]
if (type(x) === 'Function') {
if (isTruthy(x())) {
return false
}
} else if (isTruthy(x)) {
return false
}
counter++
}
return true
}
Tests
import {runTests} from 'helpers-fn'
import {allFalse} from './allFalse'
const happy = {ok: [() => false, () => [], () => {}, null, false, []]}
const withArray = {fail: [...happy.ok, [1]]}
const withObject = {fail: [...happy.ok, {a: 1}]}
const withFunction = {fail: [...happy.ok, () => ({a: 1})]}
const withBoolean = {fail: [...happy.ok, true]}
const testData = {
label: 'R.allFalse',
data: [happy, withArray, withObject, withFunction, withBoolean],
fn: input => allFalse(...input),
}
runTests(testData)
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.
const input = {
a : 1,
b : 2,
}
const predicates = [
x => x.a === 1,
x => x.b === 2,
]
const result = R.allPass(predicates)(input) // => true
Try this R.allPass example in Rambda REPL
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()
})
allTrue
allTrue(...input: any[]): boolean
It returns true
if all inputs
arguments are truthy(empty objects and empty arrays are considered falsy).
R.allTrue(1, true, {a: 1}, [1], 'foo', () => true)
// => true
Try this R.allTrue example in Rambda REPL
R.allTrue source
import {isFalsy} from './_internals/isFalsy'
import {type} from './type'
export function allTrue(...inputs) {
let counter = 0
while (counter < inputs.length) {
const x = inputs[counter]
if (type(x) === 'Function') {
if (isFalsy(x())) {
return false
}
} else if (isFalsy(x)) {
return false
}
counter++
}
return true
}
Tests
import {allTrue} from './allTrue'
test('with functions', () => {
const foo = () => 1
const bar = () => false
const baz = () => JSON.parse('{sda')
const result = allTrue(foo, bar, baz)
expect(result).toBeFalse()
})
test('usage with non boolean', () => {
const foo = {a: 1}
const baz = [1, 2, 3]
const result = allTrue(foo, foo, baz)
expect(result).toBeTrue()
})
test('usage with boolean', () => {
const foo = 4
const baz = [1, 2, 3]
const result = allTrue(foo > 2, baz.length === 3)
expect(result).toBeTrue()
})
test('escapes early - case 0', () => {
const foo = undefined
const result = allTrue(foo, () => foo.a)
expect(result).toBeFalse()
})
test('escapes early - case 1', () => {
const foo = null
const result = allTrue(foo, () => foo.a)
expect(result).toBeFalse()
})
test('escapes early - case 2', () => {
const foo = {a: 'bar'}
const result = allTrue(foo, foo.a, foo.a.b)
expect(result).toBeFalse()
})
test('escapes early - case 3', () => {
const foo = {a: {b: 'foo'}}
const result = allTrue(
foo,
() => foo.a,
() => foo.a.b
)
expect(result).toBeTrue()
})
allType
allType(targetType: RambdaTypes): (...input: any[]) => boolean
It returns a function which will return true
if all of its inputs
arguments belong to targetType
.
:boom:
targetType
is one of the possible returns ofR.type
const targetType = 'String'
const result = R.allType(
targetType
)('foo', 'bar', 'baz')
// => true
Try this R.allType example in Rambda REPL
R.allType source
import {type} from './type'
export function allType(targetType) {
return (...inputs) => {
let counter = 0
while (counter < inputs.length) {
if (type(inputs[counter]) !== targetType) {
return false
}
counter++
}
return true
}
}
Tests
import {allType} from './allType'
test('when true', () => {
const result = allType('Array')([1, 2, 3], [], [null])
expect(result).toBeTrue()
})
test('when false', () => {
const result = allType('String')(1, undefined, null, [])
expect(result).toBeFalse()
})
always
always<T>(x: T): (...args: unknown[]) => T
It returns function that always returns x
.
const fn = R.always(7)
const result = fn()
// => 7
Try this R.always example in Rambda REPL
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()
})
and
Logical AND
R.and(true, true); // => true
R.and(false, true); // => false
R.and(true, 'foo'); // => 'foo'
Try this R.and example in Rambda REPL
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
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()
})
anyFalse
anyFalse(...input: any[]): boolean
It returns true
if any of inputs
is falsy(empty objects and empty arrays are considered falsy).
R.anyFalse(1, {a: 1}, [1], () => false)
// => true
Try this R.anyFalse example in Rambda REPL
R.anyFalse source
import {isFalsy} from './_internals/isFalsy'
import {type} from './type'
export function anyFalse(...inputs) {
let counter = 0
while (counter < inputs.length) {
const x = inputs[counter]
if (type(x) === 'Function') {
if (isFalsy(x())) {
return true
}
} else if (isFalsy(x)) {
return true
}
counter++
}
return false
}
Tests
import {anyFalse} from './anyFalse'
test('when true', () => {
expect(anyFalse(true, true, false)).toBeTruthy()
})
test('when false', () => {
expect(anyFalse(true, true)).toBeFalsy()
})
test('supports function', () => {
expect(
anyFalse(
true,
() => true,
() => false
)
).toBeTruthy()
})
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
.
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
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()
})
anyTrue
anyTrue(...input: any[]): boolean
It returns true
if any of inputs
arguments are truthy(empty objects and empty arrays are considered falsy).
R.anyTrue(0, null, [], {}, '', () => true)
// => true
Try this R.anyTrue example in Rambda REPL
R.anyTrue source
import {isTruthy} from './_internals/isTruthy'
import {type} from './type'
export function anyTrue(...inputs) {
let counter = 0
while (counter < inputs.length) {
const x = inputs[counter]
if (type(x) === 'Function') {
if (isTruthy(x())) {
return true
}
} else if (isTruthy(x)) {
return true
}
counter++
}
return false
}
Tests
import {anyTrue} from './anyTrue'
test('when true', () => {
expect(anyTrue(true, true, false)).toBeTruthy()
})
test('when false', () => {
expect(anyTrue(false, false, false)).toBeFalsy()
})
test('supports function', () => {
expect(
anyTrue(
false,
false,
false,
() => false,
() => true
)
).toBeTruthy()
})
anyType
anyType(targetType: RambdaTypes): (...input: any[]) => boolean
It returns a function which will return true
if at least one of its inputs
arguments belongs to targetType
.
targetType
is one of the possible returns of R.type
:boom:
targetType
is one of the possible returns ofR.type
const targetType = 'String'
const result = R.anyType(
targetType
)(1, {}, 'foo')
// => true
Try this R.anyType example in Rambda REPL
R.anyType source
import {type} from './type'
export function anyType(targetType) {
return (...inputs) => {
let counter = 0
while (counter < inputs.length) {
if (type(inputs[counter]) === targetType) {
return true
}
counter++
}
return false
}
}
Tests
import {anyType} from './anyType'
test('when true', () => {
const result = anyType('Array')(1, undefined, null, [])
expect(result).toBeTrue()
})
test('when false', () => {
const result = anyType('String')(1, undefined, null, [])
expect(result).toBeFalse()
})
append
append<T>(x: T, list: T[]): T[]
It adds element x
at the end of list
.
const x = 'foo'
const result = R.append(x, ['bar', 'baz'])
// => ['bar', 'baz', 'foo']
Try this R.append example in Rambda REPL
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'])
})
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.
const result = R.apply(Math.max, [42, -Infinity, 1337])
// => 1337
Try this R.apply example in Rambda REPL
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)
})
applyDiff
applyDiff<Output>(rules: ApplyDiffRule[], obj: object): Output
It changes paths in an object according to a list of operations. Valid operations are add
, update
and delete
. Its use-case is while writing tests and you need to change the test data.
Note, that you cannot use update
operation, if the object path is missing in the input object.
Also, you cannot use add
operation, if the object path has a value.
const obj = {a: {b:1, c:2}}
const rules = [
{op: 'remove', path: 'a.c'},
{op: 'add', path: 'a.d', value: 4},
{op: 'update', path: 'a.b', value: 2},
]
const result = R.applyDiff(rules, obj)
const expected = {a: {b: 2, d: 4}}
// => `result` is equal to `expected`
Try this R.applyDiff example in Rambda REPL
R.applyDiff source
import {assocPath} from './assocPath'
import {path as pathModule} from './path'
const ALLOWED_OPERATIONS = ['remove', 'add', 'update']
export function removeAtPath(path, obj) {
const p = typeof path === 'string' ? path.split('.') : path
const len = p.length
if (len === 0) return
if (len === 1) return delete obj[p[0]]
if (len === 2) return delete obj[p[0]][p[1]]
if (len === 3) return delete obj[p[0]][p[1]][p[2]]
if (len === 4) return delete obj[p[0]][p[1]][p[2]][p[3]]
if (len === 5) return delete obj[p[0]][p[1]][p[2]][p[3]][p[4]]
if (len === 6) {
return delete obj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]]
}
if (len === 7) {
return delete obj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]][p[6]]
}
if (len === 8) {
return delete obj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]][p[6]][p[7]]
}
if (len === 9) {
return delete obj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]][p[6]][p[7]][p[8]]
}
if (len === 10) {
return delete obj[p[0]][p[1]][p[2]][p[3]][p[4]][p[5]][p[6]][p[7]][p[8]][
p[9]
]
}
}
export function applyDiff(rules, obj) {
if (arguments.length === 1) return _obj => applyDiff(rules, _obj)
let clone = {...obj}
rules.forEach(({op, path, value}) => {
if (!ALLOWED_OPERATIONS.includes(op)) return
if (op === 'add' && path && value !== undefined) {
if (pathModule(path, obj)) return
return (clone = assocPath(path, value, clone))
}
if (op === 'remove') {
if (pathModule(path, obj) === undefined) return
return removeAtPath(path, clone)
}
if (op === 'update' && path && value !== undefined) {
if (pathModule(path, obj) === undefined) return
return (clone = assocPath(path, value, clone))
}
})
return clone
}
Tests
import {applyDiff} from './applyDiff'
test('remove operation', () => {
const rules = [
{
op: 'remove',
path: 'a.b',
},
]
const result = applyDiff(rules, {
a: {
b: 1,
c: 2,
},
})
expect(result).toEqual({a: {c: 2}})
})
test('update operation', () => {
const rules = [
{
op: 'update',
path: 'a.b',
value: 3,
},
{
op: 'update',
path: 'a.c.1',
value: 3,
},
{
op: 'update',
path: 'a.d',
value: 3,
},
]
const result = applyDiff(rules, {
a: {
b: 1,
c: [1, 2],
},
})
expect(result).toEqual({
a: {
b: 3,
c: [1, 3],
},
})
})
test('add operation', () => {
const rules = [
{
op: 'add',
path: 'a.b',
value: 3,
},
{
op: 'add',
path: 'a.d',
value: 3,
},
]
const result = applyDiff(rules, {
a: {
b: 1,
c: 2,
},
})
expect(result).toEqual({
a: {
b: 1,
c: 2,
d: 3,
},
})
})
applySpec
applySpec<Spec extends Record<string, (...args: any[]) => any>>(
spec: Spec
): (
...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
:boom: The currying in this function works best with functions with 4 arguments or less. (arity of 4)
const fn = R.applySpec({
sum: R.add,
nested: { mul: R.multiply }
})
const result = fn(2, 4)
// => { sum: 6, nested: { mul: 8 } }
Try this R.applySpec example in Rambda REPL
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',
})
})
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
.
:boom: This copies and flattens prototype properties onto the new object as well. All non-primitive properties are copied by reference.
R.assoc('c', 3, {a: 1, b: 2})
// => {a: 1, b: 2, c: 3}
Try this R.assoc example in Rambda REPL
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}})
})
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
.
const path = 'b.c'
const newValue = 2
const obj = { a: 1 }
R.assocPath(path, newValue, obj)
// => { a : 1, b : { c : 2 }}
Try this R.assocPath example in Rambda REPL
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)
})
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.
:boom: R.bind does not provide the additional argument-binding capabilities of Function.prototype.bind.
const log = R.bind(console.log, console)
const result = R.pipe(
R.assoc('a', 2),
R.tap(log),
R.assoc('a', 3)
)({a: 1});
// => result - `{a: 3}`
// => console log - `{a: 2}`
Try this R.bind example in Rambda REPL
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)
})
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.
const firstCondition = x => x > 10
const secondCondition = x => x < 20
const fn = R.both(secondCondition)
const result = [fn(15), fn(30)]
// => [true, false]
Try this R.both example in Rambda REPL
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')
})
chain
chain<T, U>(fn: (n: T) => U[], list: T[]): U[]
The method is also known as flatMap
.
const duplicate = n => [ n, n ]
const list = [ 1, 2, 3 ]
const result = chain(duplicate, list)
// => [ 1, 1, 2, 2, 3, 3 ]
Try this R.chain example in Rambda REPL
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()
})
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
.
const result = [
R.clamp(0, 10, 5),
R.clamp(0, 10, -1),
R.clamp(0, 10, 11)
]
// => [5, 0, 10]
Try this R.clamp example in Rambda REPL
clone
It creates a deep copy of the input
, which may contain (nested) Arrays and Objects, Numbers, Strings, Booleans and Dates.
const objects = [{a: 1}, {b: 2}];
const objectsClone = R.clone(objects);
const result = [
R.equals(objects, objectsClone),
R.equals(objects[0], objectsClone[0]),
] // => [ true, true ]
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20objects%20%3D%20%5B%7Ba%3A%201%7D%2C%20%7Bb%3A%202%7D%5D%3B%0Aconst%20objectsClone%20%3D%20R.clone(objects)%3B%0A%0Aconst%20result%20%3D%20%5B%0A%20%20R.equals(objects%2C