Detalhes do pacote

rambdax

selfrefactor84.3kMIT11.3.1

Extended version of Rambda - a lightweight, faster alternative to Ramda

ramda, rambda, fp, functional

readme (leia-me)

Rambdax

Extended version of Rambda(utility library) - Documentation

Rambda is smaller and faster alternative to the popular functional programming library Ramda. - Documentation

install size GitHub contributors codecov Library size

❯ 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 9.0.0(or higher) requires TypeScript version 4.3.3(or higher).

Dot notation for R.path, R.paths, R.assocPath and R.lensPath

Standard usage of R.path is R.path(['a', 'b'], {a: {b: 1} }).

In Rambda you have the choice to use dot notation(which is arguably more readable):

R.path('a.b', {a: {b: 1} })

Comma notation for R.pick and R.omit

Similar to dot notation, but the separator is comma(,) instead of dot(.).

R.pick('a,b', {a: 1 , b: 2, c: 3} })
// No space allowed between properties

Extendable with Ramda community projects

Rambdax implements some methods from Ramda community projects, such as R.lensSatisfies, R.lensEq and R.viewOr.

Understandable source code due to little usage of internals

Ramda uses a lot of internals, which hides a lot of logic. Reading the full source code of a method can be challenging.

Better VSCode experience

If the project is written in Javascript, then go to source definition action will lead you to actual implementation of the method.

Deno support

import * as R from "https://deno.land/x/rambdax/mod.ts";

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

<summary> Click to see the full list of 46 Ramda methods not implemented in Rambda and their status. </summary> - 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 - lift - liftN - mapAccum - Ramda example doesn't looks convincing - mapAccumRight - memoizeWith - hard to imagine its usage in context of R.pipe/R.compose - 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.pipe/R.compose to add more composition methods - otherwise - naming is confusing - pair - left-pad 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. - partialRight - I dislike R.partial, so I don't want to add more methods that are based on it - pipeWith - project - naming is confusing, but also limited use case - promap - reduceRight - I find right/left methods confusing so I added them only where it makes sense. - reduceWhile - functions with 4 inputs - I think that even 3 is too much - reduced - remove - nice name but it is too generic. Also, Rambdax has such method and there it works very differently - scan - hard to explain - sequence - splitWhenever - symmetricDifferenceWith - andThen - toPairsIn - transduce - currently is out of focus - traverse - same as above - unary - uncurryN - unfold - similar to R.scan 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):

---------------

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

---------------

Differences between Rambda and Ramda

  • Rambda's type detects async functions and unresolved Promises. The returned values are 'Async' and 'Promise'.

  • Rambda's type handles NaN input, in which case it returns NaN.

  • Rambda's forEach can iterate over objects not only arrays.

  • Rambda's map, filter, partition when they iterate over objects, they pass property and input object as predicate's argument.

  • Rambda's filter returns empty array with bad input(null 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.

---------------

❯ Benchmarks

<summary> 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). </summary> 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 | 🔳

---------------

❯ Used by

---------------

API

add

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

---------------

addIndex

Try this R.addIndex example in Rambda REPL

---------------

addIndexRight

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

---------------

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]).

const result = R.adjust(
  0,
  a => a + 1,
  [0, 100]
) // => [1, 100]

Try this R.adjust example in Rambda REPL

<summary>R.adjust source</summary> 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)
<summary>Tests</summary> 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) })

---------------

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

<summary>R.all source</summary> 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 }
<summary>Tests</summary> 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() })

---------------

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

<summary>R.allFalse source</summary> 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 }
<summary>Tests</summary> 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)

---------------

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

<summary>R.allPass source</summary> javascript export function allPass(predicates){ return (...input) => { let counter = 0 while (counter < predicates.length){ if (!predicates[ counter ](...input)){ return false } counter++ } return true } }
<summary>Tests</summary> 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() })

---------------

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

<summary>R.allTrue source</summary> 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 }
<summary>Tests</summary> 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() })

---------------

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 of R.type

const targetType = 'String'

const result = R.allType(
  targetType
)('foo', 'bar', 'baz')
// => true

Try this R.allType example in Rambda REPL

<summary>R.allType source</summary> 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 } }
<summary>Tests</summary> 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() })

---------------

always

It returns function that always returns x.

Try this R.always example in Rambda REPL

---------------

and

Logical AND

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

<summary>R.any source</summary> 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 }
<summary>Tests</summary> 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() })

---------------

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

<summary>R.anyFalse source</summary> 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 }
<summary>Tests</summary> 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() })

---------------

anyPass


anyPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean

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

<summary>R.anyPass source</summary> javascript export function anyPass(predicates){ return (...input) => { let counter = 0 while (counter < predicates.length){ if (predicates[ counter ](...input)){ return true } counter++ } return false } }
<summary>Tests</summary> 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() })

---------------

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

<summary>R.anyTrue source</summary> 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 }
<summary>Tests</summary> 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() })

---------------

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 of R.type

const targetType = 'String'

const result = R.anyType(
  targetType
)(1, {}, 'foo')
// => true

Try this R.anyType example in Rambda REPL

<summary>R.anyType source</summary> 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 } }
<summary>Tests</summary> 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() })

---------------

ap


ap<T, U>(fns: Array<(a: T) => U>[], vs: T[]): U[]

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.

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

<summary>R.ap source</summary> javascript export function ap(functions, input){ if (arguments.length === 1){ return _inputs => ap(functions, _inputs) } return functions.reduce((acc, fn) => [ ...acc, ...input.map(fn) ], []) }
<summary>Tests</summary> 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 ]) })

---------------

aperture


aperture<N extends number, T>(n: N, list: T[]): Array<Tuple<T, N>> | []

It returns a new list, composed of consecutive n-tuples from a list.

const result = R.aperture(2, [1, 2, 3, 4])
// => [[1, 2], [2, 3], [3, 4]]

Try this R.aperture example in Rambda REPL

<summary>R.aperture source</summary> 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 }
<summary>Tests</summary> 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([]) })

---------------

append


append<T>(xToAppend: T, iterable: T[]): T[]

It adds element x at the end of iterable.

const x = 'foo'

const result = R.append(x, ['bar', 'baz'])
// => ['bar', 'baz', 'foo']

Try this R.append example in Rambda REPL

<summary>R.append source</summary> 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 }
<summary>Tests</summary> 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' ]) })

---------------

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

<summary>R.apply source</summary> javascript export function apply(fn, args){ if (arguments.length === 1){ return _args => apply(fn, _args) } return fn.apply(this, args) }
<summary>Tests</summary> 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() })

---------------

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, Record<string, unknown>)
const expected = {a: {b: 2, d: 4}}

// => `result` is equal to `expected`

Try this R.applyDiff example in Rambda REPL

<summary>R.applyDiff source</summary> 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 }
<summary>Tests</summary> 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, }, }) })

---------------

applySpec


applySpec<Spec extends Record<string, AnyFunction>>(
  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

<summary>R.applySpec source</summary> 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 ](...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 }
<summary>Tests</summary> 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', }) })

---------------

applyTo

Try this R.applyTo example in Rambda REPL

---------------

ascend

Try this R.ascend example in Rambda REPL

---------------

assoc

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

---------------

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 }

const result = R.assocPath(path, newValue, obj)
// => { a : 1, b : { c : 2 }}

Try this R.assocPath example in Rambda REPL

<summary>R.assocPath source</summary> 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)
<summary>Tests</summary> 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) })

---------------

binary

Try this R.binary example in Rambda REPL

---------------

bind


bind<F extends AnyFunction, 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

<summary>R.bind source</summary> 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)) }
<summary>Tests</summary> 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) })

---------------

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(firstCondition, secondCondition)

const result = [fn(15), fn(30)]
// => [true, false]

Try this R.both example in Rambda REPL

<summary>R.both source</summary> javascript export function both(f, g){ if (arguments.length === 1) return _g => both(f, _g) return (...input) => f(...input) && g(...input) }
<summary>Tests</summary>

```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