Extended version of Rambda - a lightweight, faster alternative to Ramda
npm install rambdaxExtended version of Rambda(utility library) - Documentation
Rambda is smaller and faster alternative to the popular functional programming library Ramda. - Documentation



!Library size
Rambdax passthrough all Rambda methods and introduce some new functions.
The idea of Rambdax is to extend Rambda without worring for Ramda compatibility.

``javascript
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
* Differences between Rambda and Ramda
* API
* Changelog

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 9.0.0(or higher) requires TypeScript version 4.3.3(or higher).
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} })
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
Rambdax implements some methods from Ramda community projects, such as R.lensSatisfies, R.lensEq and R.viewOr.
Ramda uses a lot of internals, which hides a lot of logic. Reading the full source code of a method can be challenging.
If the project is written in Javascript, then go to source definition action will lead you to actual implementation of the method.
``
import * as R from "https://deno.land/x/rambdax/mod.ts";
Alternative TS definitions are available as rambdax/immutable. These are Rambdax definitions linted with ESLint functional/prefer-readonly-type plugin.

Click to see the full list of 46 Ramda methods not implemented in Rambda and their status.
- construct - Using classes is not very functional programming oriented.
- constructN - same as above
- into - no support for transducer as it is overly complex to implement, understand and read.
- invert - overly complicated and limited use case
- invertObj
- invoker
- keysIn - we shouldn't encourage extending object with .prototype Ramda
- lift
- liftN
- mapAccum - example doesn't looks convincingR.pipe
- mapAccumRight
- memoizeWith - hard to imagine its usage in context of /R.composeR.pipe
- mergeDeepWith - limited use case
- mergeDeepWithKey
- mergeWithKey
- nAry - hard to argument about and hard to create meaningful TypeScript definitions
- nthArg - limited use case
- o - enough TypeScript issues with /R.compose to add more composition methodsleft-pad
- otherwise - naming is confusing
- pair - types of debacles happens partially because of such methods that should not be hidden, bur rather part of your code base even if they need to exist.R.partial
- partialRight - I dislike , so I don't want to add more methods that are based on itright/left
- pipeWith
- project - naming is confusing, but also limited use case
- promap
- reduceRight - I find methods confusing so I added them only where it makes sense.Rambdax
- reduceWhile - functions with 4 inputs - I think that even 3 is too much
- reduced
- remove - nice name but it is too generic. Also, has such method and there it works very differentlyR.scan
- scan - hard to explain
- sequence
- splitWhenever
- symmetricDifferenceWith
- andThen
- toPairsIn
- transduce - currently is out of focus
- traverse - same as above
- unary
- uncurryN
- unfold - similar to and I find that it doesn't help with readability
- unionWith - why it has its usage, I want to limit number of methods that accept more than 2 arguments
- until
- useWith - hard to explain
- valuesIn
- xprod - limited use case
- thunkify
- __ - placeholder method allows user to further customize the method call. While, it seems useful initially, the price is too high in terms of complexity for TypeScript definitions. If it is not easy exressable in TypeScript, it is not worth it as Rambda is a TypeScript first library.
The following methods are not going to be added(reason for exclusion is provided as a comment):

- 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 {add} from "https://deno.land/x/rambda/mod.ts";

- 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 or undefined), 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.

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.30.1).
method | Rambda | Ramda | Lodash
--- |--- | --- | ---
add | 🚀 Fastest | 21.52% slower | 82.15% slower
adjust | 8.48% slower | 🚀 Fastest | 🔳
all | 🚀 Fastest | 7.18% slower | 🔳
allPass | 🚀 Fastest | 88.25% 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 | 6.07% slower | 16.89% slower | 🚀 Fastest
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 | 85.68% slower | 3.57% slower
ifElse | 🚀 Fastest | 58.56% slower | 🔳
includes | 🚀 Fastest | 81.64% slower | 🔳
indexOf | 🚀 Fastest | 80.17% 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 | 🚀 Fastest | 0.11% slower | 🔳
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 | 84.9% slower | 🔳
uniqBy | 51.93% slower | 🚀 Fastest | 🔳
uniqWith | 8.29% slower | 🚀 Fastest | 🔳
uniqWith | 14.23% slower | 🚀 Fastest | 🔳
update | 🚀 Fastest | 52.35% slower | 🔳
view | 🚀 Fastest | 76.15% slower | 🔳

- Walmart Canada reported by w-b-dev
- Rewrite of the Betaflight configurator

It adds a and b.
> :boom: It doesn't work with strings, as the inputs are parsed to numbers before calculation.
Try this R.add example in Rambda REPL

Try this R.addIndex example in Rambda REPL

Same as R.addIndex, but it will passed indexes are decreasing, instead of increasing.

`typescript
adjust
`
It replaces index in array list with the result of replaceFn(list[i]).
`javascript`
const result = R.adjust(
0,
a => a + 1,
[0, 100]
) // => [1, 100]
Try this R.adjust example in Rambda REPL
`javascript
import { cloneList } from './_internals/cloneList.js'
import { curry } from './curry.js'
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)
`
`javascript
import { add } from './add.js'
import { adjust } from './adjust.js'
import { pipe } from './pipe.js'
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)
})
`

`typescript
all
`
It returns true, if all members of array list returns true, when applied as argument to predicate function.
`javascript
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
`javascript
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
}
`
`javascript
import { all } from './all.js'
const list = [ 0, 1, 2, 3, 4 ]
test('when true', () => {
const fn = x => x > -1
expect(all(fn)(list)).toBeTrue()
})
test('when false', () => {
const fn = x => x > 2
expect(all(fn, list)).toBeFalse()
})
`

`typescript
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
`javascript`
R.allFalse(0, null, [], {}, '', () => false)
// => true
Try this R.allFalse example in Rambda REPL
`javascript
import { isTruthy } from './_internals/isTruthy.js'
import { type } from './type.js'
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
}
`
`javascript
import { runTests } from 'helpers-fn'
import { allFalse } from './allFalse.js'
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)
`

`typescript
allPass
`
It returns true, if all functions of predicates return true, when input is their argument.
`javascript`
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
`javascript
export function allPass(predicates){
return (...input) => {
let counter = 0
while (counter < predicates.length){
if (!predicates counter ){
return false
}
counter++
}
return true
}
}
`
`javascript
import { allPass } from './allPass.js'
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', () => {
const fn = function (
w, x, y, z
){
return w + x === y + z
}
expect(allPass([ fn ])(
3, 3, 3, 3
)).toBeTrue()
})
`

`typescript
allTrue(...input: any[]): boolean
`
It returns true if all inputs arguments are truthy(empty objects and empty arrays are considered falsy).
`javascript`
R.allTrue(1, true, {a: 1}, [1], 'foo', () => true)
// => true
Try this R.allTrue example in Rambda REPL
`javascript
import { isFalsy } from './_internals/isFalsy.js'
import { type } from './type.js'
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
}
`
`javascript
import { allTrue } from './allTrue.js'
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()
})
`

`typescript
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 of R.type
`javascript
const targetType = 'String'
const result = R.allType(
targetType
)('foo', 'bar', 'baz')
// => true
`
Try this R.allType example in Rambda REPL
`javascript
import { type } from './type.js'
export function allType(targetType){
return (...inputs) => {
let counter = 0
while (counter < inputs.length){
if (type(inputs[ counter ]) !== targetType){
return false
}
counter++
}
return true
}
}
`
`javascript
import { allType } from './allType.js'
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()
})
`

It returns function that always returns x.
Try this R.always example in Rambda REPL

Logical AND
Try this R.and example in Rambda REPL

`typescript
any
`
It returns true, if at least one member of list returns true, when passed to a predicate function.
`javascript`
const list = [1, 2, 3]
const predicate = x => x * x > 8
R.any(fn, list)
// => true
Try this R.any example in Rambda REPL
`javascript
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
}
`
`javascript
import { any } from './any.js'
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
anyFalse(...input: any[]): boolean
`
It returns true if any of inputs is falsy(empty objects and empty arrays are considered falsy).
`javascript`
R.anyFalse(1, {a: 1}, [1], () => false)
// => true
Try this R.anyFalse example in Rambda REPL
`javascript
import { isFalsy } from './_internals/isFalsy.js'
import { type } from './type.js'
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
}
`
`javascript
import { anyFalse } from './anyFalse.js'
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()
})
`

`typescript
anyPass
`
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.
`javascript
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
`javascript
export function anyPass(predicates){
return (...input) => {
let counter = 0
while (counter < predicates.length){
if (predicates counter ){
return true
}
counter++
}
return false
}
}
`
`javascript
import { anyPass } from './anyPass.js'
test('happy', () => {
const rules = [ x => typeof x === 'string', x => x > 10 ]
const predicate = anyPass(rules)
expect(predicate('foo')).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)).toBeFalse()
})
test('works with multiple inputs', () => {
const 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
anyTrue(...input: any[]): boolean
`
It returns true if any of inputs arguments are truthy(empty objects and empty arrays are considered falsy).
`javascript`
R.anyTrue(0, null, [], {}, '', () => true)
// => true
Try this R.anyTrue example in Rambda REPL
`javascript
import { isTruthy } from './_internals/isTruthy.js'
import { type } from './type.js'
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
}
`
`javascript
import { anyTrue } from './anyTrue.js'
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()
})
`

`typescript
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 of R.type
`javascript
const targetType = 'String'
const result = R.anyType(
targetType
)(1, {}, 'foo')
// => true
`
Try this R.anyType example in Rambda REPL
`javascript
import { type } from './type.js'
export function anyType(targetType){
return (...inputs) => {
let counter = 0
while (counter < inputs.length){
if (type(inputs[ counter ]) === targetType){
return true
}
counter++
}
return false
}
}
`
`javascript
import { anyType } from './anyType.js'
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()
})
`

`typescript
ap
`
It takes a list of functions and a list of values. Then it returns a list of values obtained by applying each function to each value.
`javascript`
const result = R.ap(
[
x => x + 1,
x => x + 2,
],
[1, 2, 3]
)
// => [2, 3, 4, 3, 4, 5]
Try this R.ap example in Rambda REPL
`javascript
export function ap(functions, input){
if (arguments.length === 1){
return _inputs => ap(functions, _inputs)
}
return functions.reduce((acc, fn) => [ ...acc, ...input.map(fn) ], [])
}
`
`javascript
import { ap } from './ap.js'
function mult2(x){
return x * 2
}
function plus3(x){
return x + 3
}
test('happy', () => {
expect(ap([ mult2, plus3 ], [ 1, 2, 3 ])).toEqual([ 2, 4, 6, 4, 5, 6 ])
})
`

`typescript
aperture
`
It returns a new list, composed of consecutive n-tuples from a list.
`javascript`
const result = R.aperture(2, [1, 2, 3, 4])
// => [[1, 2], [2, 3], [3, 4]]
Try this R.aperture example in Rambda REPL
`javascript
export function aperture(step, list){
if (arguments.length === 1){
return _list => aperture(step, _list)
}
if (step > list.length) return []
let idx = 0
const limit = list.length - (step - 1)
const acc = new Array(limit)
while (idx < limit){
acc[ idx ] = list.slice(idx, idx + step)
idx += 1
}
return acc
}
`
`javascript
import { aperture } from './aperture.js'
const list = [ 1, 2, 3, 4, 5, 6, 7 ]
test('happy', () => {
expect(aperture(1, list)).toEqual([ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ], [ 6 ], [ 7 ] ])
expect(aperture(2, list)).toEqual([
[ 1, 2 ],
[ 2, 3 ],
[ 3, 4 ],
[ 4, 5 ],
[ 5, 6 ],
[ 6, 7 ],
])
expect(aperture(3, list)).toEqual([
[ 1, 2, 3 ],
[ 2, 3, 4 ],
[ 3, 4, 5 ],
[ 4, 5, 6 ],
[ 5, 6, 7 ],
])
expect(aperture(8, list)).toEqual([])
})
`

`typescript
append
`
It adds element x at the end of iterable.
`javascript
const x = 'foo'
const result = R.append(x, ['bar', 'baz'])
// => ['bar', 'baz', 'foo']
`
Try this R.append example in Rambda REPL
`javascript
import { cloneList } from './_internals/cloneList.js'
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
}
`
`javascript
import { append } from './append.js'
test('happy', () => {
expect(append('tests', [ 'write', 'more' ])).toEqual([
'write',
'more',
'tests',
])
})
test('append to empty array', () => {
expect(append('tests')([])).toEqual([ 'tests' ])
})
test('with strings', () => {
expect(append('o', 'fo')).toEqual([ 'f', 'o', 'o' ])
})
`

`typescript
apply
`
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.
`javascript`
const result = R.apply(Math.max, [42, -Infinity, 1337])
// => 1337
Try this R.apply example in Rambda REPL
`javascript
export function apply(fn, args){
if (arguments.length === 1){
return _args => apply(fn, _args)
}
return fn.apply(this, args)
}
`
`javascript
import { apply } from './apply.js'
import { bind } from './bind.js'
import { identity } from './identity.js'
test('happy', () => {
expect(apply(identity, [ 1, 2, 3 ])).toBe(1)
})
test('applies function to argument list', () => {
expect(apply(Math.max, [ 1, 2, 3, -99, 42, 6, 7 ])).toBe(42)
})
test('provides no way to specify context', () => {
const obj = {
method (){
return this === obj
},
}
expect(apply(obj.method, [])).toBeFalse()
expect(apply(bind(obj.method, obj), [])).toBeTrue()
})
`

`typescript
applyDiff
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.add
Also, you cannot use operation, if the object path has a value.
`javascript
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, Record
const expected = {a: {b: 2, d: 4}}
// => result is equal to expected`
Try this R.applyDiff example in Rambda REPL
`javascript
import { createPath } from './_internals/createPath.js'
import { assocPathFn } from './assocPath.js'
import { path as pathModule } from './path.js'
const ALLOWED_OPERATIONS = [ 'remove', 'add', 'update' ]
export function removeAtPath(path, obj){
const p = createPath(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
clone = assocPathFn(
path, value, clone
)
return
}
if (op === 'remove'){
if (pathModule(path, obj) === undefined) return
removeAtPath(path, clone)
return
}
if (op === 'update' && path && value !== undefined){
if (pathModule(path, obj) === undefined) return
clone = assocPathFn(
path, value, clone
)
}
})
return clone
}
`
`javascript
import { applyDiff } from './applyDiff.js'
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,
},
]
expect(applyDiff(rules, {
a : {
b : 1,
c : [ 1, 2 ],
},
})).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,
},
})
})
`

`typescript
applySpec
spec: Spec
): (
...args: Parameters
) => { [Key in keyof Spec]: ReturnType
`
> :boom: The currying in this function works best with functions with 4 arguments or less. (arity of 4)
`javascript`
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
`javascript
import { isArray } from './_internals/isArray.js'
// recursively traverse the given spec object to find the highest arity function
export 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
}
}
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
}
}
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
}
`
`javascript
import { applySpec as applySpecRamda, nAry } from 'ramda'
import {
add,
always,
compose,
dec,
inc,
map,
path,
prop,
T,
} from '../rambda.js'
import { applySpec } from './applySpec.js'
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).toHaveLength(0)
expect(fRamda).toHaveLength(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',
})
})
`

Try this R.applyTo example in Rambda REPL

Try this R.ascend example in Rambda REPL

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.
Try this R.assoc example in Rambda REPL

`typescript
assocPath
It makes a shallow clone of obj with setting or overriding with newValue the property found with path.
`javascript
const path = 'b.c'
const newValue = 2
const obj = { a: 1 }
const result = R.assocPath(path, newValue, obj)
// => { a : 1, b : { c : 2 }}
`
Try this R.assocPath example in Rambda REPL
`javascript
import { cloneList } from './_internals/cloneList.js'
import { createPath } from './_internals/createPath.js'
import { isArray } from './_internals/isArray.js'
import { isIndexInteger } from './_internals/isInteger.js'
import { assocFn } from './assoc.js'
import { curry } from './curry.js'
export function assocPathFn(
path, newValue, input
){
const pathArrValue = createPath(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 ?
isIndexInteger(pathArrValue[ 1 ]) ?
[] :
{} :
input[ index ]
newValue = assocPathFn(
Array.prototype.slice.call(pathArrValue, 1),
newValue,
nextInput
)
}
if (isIndexInteger(index) && isArray(input)){
const arr = cloneList(input)
arr[ index ] = newValue
return arr
}
return assocFn(
index, newValue, input
)
}
export const assocPath = curry(assocPathFn)
`
`javascript
import { assocPathFn } from './assocPath.js'
test('happy', () => {
const path = 'a.c.1'
const input = {
a : {
b : 1,
c : [ 1, 2 ],
},
}
assocPathFn(
path, 3, input
)
expect(input).toEqual({
a : {
b : 1,
c : [ 1, 2 ],
},
})
})
test('string can be used as path input', () => {
const testObj = {
a : [ { b : 1 }, { b : 2 } ],
d : 3,
}
const result1 = assocPathFn(
[ 'a', 0, 'b' ], 10, testObj
)
const result2 = assocPathFn(
'a.0.b', 10, testObj
)
const expected = {
a : [ { b : 10 }, { b : 2 } ],
d : 3,
}
expect(result1).toEqual(expected)
expect(result2).toEqual(expected)
})
test('difference with ramda - doesn\'t overwrite primitive values with keys in the path', () => {
const obj = { a : 'str' }
const result = assocPathFn(
[ 'a', 'b' ], 42, obj
)
expect(result).toEqual({
a : {
0 : 's',
1 : 't',
2 : 'r',
b : 42,
},
})
})
test('adds a key to an empty object', () => {
expect(assocPathFn(
[ 'a' ], 1, {}
)).toEqual({ a : 1 })
})
test('adds a key to a non-empty object', () => {
expect(assocPathFn(
'b', 2, { a : 1 }
)).toEqual({
a : 1,
b : 2,
})
})
test('adds a nested key to a non-empty object', () => {
expect(assocPathFn(
'b.c', 2, { a : 1 }
)).toEqual({
a : 1,
b : { c : 2 },
})
})
test('adds a nested key to a nested non-empty object', () => {
expect(assocPathFn('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', () => {
expect(assocPathFn('b', 2, { a : 1 })).toEqual({
a : 1,
b : 2,
})
})
test('adds a nested key to a non-empty object', () => {
expect(assocPathFn('b.c', 2, { a : 1 })).toEqual({
a : 1,
b : { c : 2 },
})
})
test('changes an existing key', () => {
expect(assocPathFn(
'a', 2, { a : 1 }
)).toEqual({ a : 2 })
})
test('undefined is considered an empty object', () => {
expect(assocPathFn(
'a', 1, undefined
)).toEqual({ a : 1 })
})
test('null is considered an empty object', () => {
expect(assocPathFn(
'a', 1, null
)).toEqual({ a : 1 })
})
test('value can be null', () => {
expect(assocPathFn(
'a', null, null
)).toEqual({ a : null })
})
test('value can be undefined', () => {
expect(assocPathFn(
'a', undefined, null
)).toEqual({ a : undefined })
})
test('assignment is shallow', () => {
expect(assocPathFn(
'a', { b : 2 }, { a : { c : 3 } }
)).toEqual({ a : { b : 2 } })
})
test('empty array as path', () => {
const result = assocPathFn(
[], 3, {
a : 1,
b : 2,
}
)
expect(result).toBe(3)
})
test('happy', () => {
const expected = { foo : { bar : { baz : 42 } } }
const result = assocPathFn(
[ 'foo', 'bar', 'baz' ], 42, { foo : null }
)
expect(result).toEqual(expected)
})
`

Try this R.binary example in Rambda REPL

`typescript
bind
`
Creates a function that is bound to a context.
> :boom: R.bind does not provide the additional argument-binding capabilities of Function.prototype.bind.
`javascript{a: 3}
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: 2}
// => console log - `
Try this R.bind example in Rambda REPL
`javascript
import { curryN } from './curryN.js'
export function bind(fn, thisObj){
if (arguments.length === 1){
return _thisObj => bind(fn, _thisObj)
}
return curryN(fn.length, (...args) => fn.apply(thisObj, args))
}
`
`javascript
import { bind } from './bind.js'
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', () => {
expect(typeof bind(add)(Foo)).toBe('function')
})
test('returns a function bound to the specified context object', () => {
const f = new Foo(12)
function isFoo(){
return this instanceof Foo
}
const isFooBound = bind(isFoo, f)
expect(isFoo()).toBeFalse()
expect(isFooBound()).toBeTrue()
})
test('works with built-in types', () => {
const abc = bind(String.prototype.toLowerCase, 'ABCDEFG')
expect(typeof abc).toBe('function')
expect(abc()).toBe('abcdefg')
})
test('works with user-defined types', () => {
const f = new Foo(12)
function getX(){
return this.x
}
const getXFooBound = bind(getX, f)
expect(getXFooBound()).toBe(12)
})
test('works with plain objects', () => {
const pojso = { x : 100 }
function incThis(){
return this.x + 1
}
const incPojso = bind(incThis, pojso)
expect(typeof incPojso).toBe('function')
expect(incPojso()).toBe(101)
})
test('does not interfere with existing object methods', () => {
const b = new Bar('a', 'b')
function getX(){
return this.x
}
const getXBarBound = bind(getX, b)
expect(b.getX()).toBe('prototype getX')
expect(getXBarBound()).toBe('a')
})
test('preserves arity', () => {
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, {})).toHaveLength(0)
expect(bind(f1, {})).toHaveLength(1)
expect(bind(f2, {})).toHaveLength(2)
expect(bind(f3, {})).toHaveLength(3)
})
`

`typescript
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.
`javascript
const firstCondition = x => x > 10
const secondCondition = x => x < 20
const fn = R.both(firstCondition, secondCondition)
const result = [fn(15), fn(30)]
// => [true, false]
`
Try this R.both example in Rambda REPL
`javascript
export function both(f, g){
if (arguments.length === 1) return _g => both(f, _g)
return (...input) => f(...input) && g(...input)
}
`
``javascript
import { both } from './both.js'
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 sec