Rambda

Faster and smaller alternative to Ramda

README

Rambda


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

❯ Example use


  1. ```javascript
  2. import { compose, map, filter } from 'rambda'

  3. const result = compose(
  4.   map(x => x * 2),
  5.   filter(x => x > 2)
  6. )([1, 2, 3, 4])
  7. // => [6, 8]
  8. ```

You can test this example in Rambda's REPL


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

❯ Rambda's advantages


Typescript included


Typescript definitions are included in the library, in comparison to Ramda, where you need to additionally install @types/ramda.

Still, you need to be aware that functional programming features in Typescript are in development, which means that using R.compose/R.pipe can be problematic.

Important - Rambda version 7.1.0(or higher) requires Typescript version 4.3.3(or higher).

Immutable TS definitions


You can use immutable version of Rambda definitions, which is linted with ESLint functional/prefer-readonly-type plugin.

  1. ```
  2. import {add} from 'rambda/immutable'
  3. ```

Deno support


While Ramda is available for Deno users, Rambda provides you with included TS definitions:

  1. ```
  2. import * as R from "https://x.nest.land/rambda@7.1.0/mod.ts";
  3. import * as Ramda from "https://x.nest.land/ramda@0.28.0/mod.ts";

  4. R.add(1)('foo') // => will trigger warning in VSCode
  5. Ramda.add(1)('foo') // => will not trigger warning in VSCode
  6. ```

Smaller size


The size of a library affects not only the build bundle size but also the dev bundle size and build time. This is important advantage, expecially for big projects.


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):

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

Comma notation for R.pick and R.omit


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

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

Speed


Rambda is generally more performant than Ramda as the benchmarks can prove that.

Support


As the library is smaller than Ramda, issues are much faster resolved.

Closing the issue is usually accompanied by publishing a new patch version of Rambda to NPM.

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

❯ Missing Ramda methods


  Click to see the full list of 76 Ramda methods not implemented in Rambda

- __
- addIndex
- ap
- aperture
- applyTo
- ascend
- binary
- call
- collectBy
- comparator
- composeWith
- construct
- constructN
- descend
- differenceWith
- dissocPath
- empty
- eqBy
- forEachObjIndexed
- gt
- gte
- hasIn
- innerJoin
- insert
- insertAll
- into
- invert
- invertObj
- invoker
- keysIn
- lift
- liftN
- lt
- lte
- mapAccum
- mapAccumRight
- memoizeWith
- mergeDeepLeft
- mergeDeepWith
- mergeDeepWithKey
- mergeWithKey
- nAry
- nthArg
- o
- otherwise
- pair
- partialRight
- pathSatisfies
- pickBy
- pipeWith
- project
- promap
- reduceBy
- reduceRight
- reduceWhile
- reduced
- remove
- scan
- sequence
- sortWith
- splitWhenever
- symmetricDifferenceWith
- andThen
- toPairsIn
- transduce
- traverse
- unary
- uncurryN
- unfold
- unionWith
- until
- useWith
- valuesIn
- xprod
- thunkify
- default


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

❯ Install


- yarn add rambda

- For UMD usage either use ./dist/rambda.umd.js or the following CDN link:

  1. ```
  2. https://unpkg.com/rambda@CURRENT_VERSION/dist/rambda.umd.js
  3. ```

- with deno

  1. ```
  2. import {compose, add} from 'https://raw.githubusercontent.com/selfrefactor/rambda/master/dist/rambda.esm.js'
  3. ```

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

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.

If you need more Ramda methods in Rambda, you may either submit a PR or check the extended version of Rambda - Rambdax. In case of the former, you may want to consult with Rambda contribution guidelines.


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

❯ Benchmarks



Click to expand all benchmark results

There are methods which are benchmarked only with Ramda and Rambda(i.e. no Lodash).

Note that some of these methods, are called with and without curring. This is done in order to give more detailed performance feedback.

The benchmarks results are produced from latest versions of Rambda, Lodash(4.17.21) and Ramda(0.28.0).


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 | 6.56% slower | 86.64% slower | 🚀 Fastest
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 | 18.09% 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


It adds a and b.

Try this R.add example in Rambda REPL

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

adjust


  1. ```typescript

  2. adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[]
  3. ```

It replaces index in array list with the result of replaceFn(list[i]).

Try this R.adjust example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[];
  3. adjust<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
  4. ```



R.adjust source

  1. ```javascript
  2. import { cloneList } from './_internals/cloneList.js'
  3. import { curry } from './curry.js'

  4. function adjustFn(
  5.   index, replaceFn, list
  6. ){
  7.   const actualIndex = index < 0 ? list.length + index : index
  8.   if (index >= list.length || actualIndex < 0) return list

  9.   const clone = cloneList(list)
  10.   clone[ actualIndex ] = replaceFn(clone[ actualIndex ])

  11.   return clone
  12. }

  13. export const adjust = curry(adjustFn)
  14. ```



Tests

  1. ```javascript
  2. import { add } from './add.js'
  3. import { adjust } from './adjust.js'
  4. import { pipe } from './pipe.js'

  5. const list = [ 0, 1, 2 ]
  6. const expected = [ 0, 11, 2 ]

  7. test('happy', () => {})

  8. test('happy', () => {
  9.   expect(adjust(
  10.     1, add(10), list
  11.   )).toEqual(expected)
  12. })

  13. test('with curring type 1 1 1', () => {
  14.   expect(adjust(1)(add(10))(list)).toEqual(expected)
  15. })

  16. test('with curring type 1 2', () => {
  17.   expect(adjust(1)(add(10), list)).toEqual(expected)
  18. })

  19. test('with curring type 2 1', () => {
  20.   expect(adjust(1, add(10))(list)).toEqual(expected)
  21. })

  22. test('with negative index', () => {
  23.   expect(adjust(
  24.     -2, add(10), list
  25.   )).toEqual(expected)
  26. })

  27. test('when index is out of bounds', () => {
  28.   const list = [ 0, 1, 2, 3 ]
  29.   expect(adjust(
  30.     4, add(1), list
  31.   )).toEqual(list)
  32.   expect(adjust(
  33.     -5, add(1), list
  34.   )).toEqual(list)
  35. })
  36. ```


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

all


  1. ```typescript

  2. all<T>(predicate: (x: T) => boolean, list: T[]): boolean
  3. ```

It returns true, if all members of array list returns true, when applied as argument to predicate function.

Try this R.all example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. all<T>(predicate: (x: T) => boolean, list: T[]): boolean;
  3. all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
  4. ```



R.all source

  1. ```javascript
  2. export function all(predicate, list){
  3.   if (arguments.length === 1) return _list => all(predicate, _list)

  4.   for (let i = 0; i < list.length; i++){
  5.     if (!predicate(list[ i ])) return false
  6.   }

  7.   return true
  8. }
  9. ```



Tests

  1. ```javascript
  2. import { all } from './all.js'

  3. const list = [ 0, 1, 2, 3, 4 ]

  4. test('when true', () => {
  5.   const fn = x => x > -1

  6.   expect(all(fn)(list)).toBeTrue()
  7. })

  8. test('when false', () => {
  9.   const fn = x => x > 2

  10.   expect(all(fn, list)).toBeFalse()
  11. })
  12. ```



Typescript test

  1. ```typescript
  2. import {all} from 'rambda'

  3. describe('all', () => {
  4.   it('happy', () => {
  5.     const result = all(
  6.       x => {
  7.         x // $ExpectType number
  8.         return x > 0
  9.       },
  10.       [1, 2, 3]
  11.     )
  12.     result // $ExpectType boolean
  13.   })
  14.   it('curried needs a type', () => {
  15.     const result = all<number>(x => {
  16.       x // $ExpectType number
  17.       return x > 0
  18.     })([1, 2, 3])
  19.     result // $ExpectType boolean
  20.   })
  21. })
  22. ```


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

allPass


  1. ```typescript

  2. allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean
  3. ```

It returns true, if all functions of predicates return true, when input is their argument.

Try this R.allPass example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
  3. allPass<T>(predicates: ((...inputs: T[]) => boolean)[]): (...inputs: T[]) => boolean;
  4. ```



R.allPass source

  1. ```javascript
  2. export function allPass(predicates){
  3.   return (...input) => {
  4.     let counter = 0
  5.     while (counter < predicates.length){
  6.       if (!predicates[ counter ](...input)){
  7.         return false
  8.       }
  9.       counter++
  10.     }

  11.     return true
  12.   }
  13. }
  14. ```



Tests

  1. ```javascript
  2. import { allPass } from './allPass.js'

  3. test('happy', () => {
  4.   const rules = [ x => typeof x === 'number', x => x > 10, x => x * 7 < 100 ]

  5.   expect(allPass(rules)(11)).toBeTrue()

  6.   expect(allPass(rules)(undefined)).toBeFalse()
  7. })

  8. test('when returns true', () => {
  9.   const conditionArr = [ val => val.a === 1, val => val.b === 2 ]

  10.   expect(allPass(conditionArr)({
  11.     a : 1,
  12.     b : 2,
  13.   })).toBeTrue()
  14. })

  15. test('when returns false', () => {
  16.   const conditionArr = [ val => val.a === 1, val => val.b === 3 ]

  17.   expect(allPass(conditionArr)({
  18.     a : 1,
  19.     b : 2,
  20.   })).toBeFalse()
  21. })

  22. test('works with multiple inputs', () => {
  23.   const fn = function (
  24.     w, x, y, z
  25.   ){
  26.     return w + x === y + z
  27.   }
  28.   expect(allPass([ fn ])(
  29.     3, 3, 3, 3
  30.   )).toBeTrue()
  31. })
  32. ```



Typescript test

  1. ```typescript
  2. import {allPass, filter} from 'rambda'

  3. describe('allPass', () => {
  4.   it('happy', () => {
  5.     const x = allPass<number>([
  6.       y => {
  7.         y // $ExpectType number
  8.         return typeof y === 'number'
  9.       },
  10.       y => {
  11.         return y > 0
  12.       },
  13.     ])(11)

  14.     x // $ExpectType boolean
  15.   })
  16.   it('issue #642', () => {
  17.     const isGreater = (num: number) => num > 5
  18.     const pred = allPass([isGreater])
  19.     const xs = [0, 1, 2, 3]

  20.     const filtered1 = filter(pred)(xs)
  21.     filtered1 // $ExpectType number[]
  22.     const filtered2 = xs.filter(pred)
  23.     filtered2 // $ExpectType number[]
  24.   })
  25.   it('issue #604', () => {
  26.     const plusEq = function(w: number, x: number, y: number, z: number) {
  27.       return w + x === y + z
  28.     }
  29.     const result = allPass([plusEq])(3, 3, 3, 3)

  30.     result // $ExpectType boolean
  31.   })
  32. })
  33. ```


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

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


  1. ```typescript

  2. any<T>(predicate: (x: T) => boolean, list: T[]): boolean
  3. ```

It returns true, if at least one member of list returns true, when passed to a predicate function.

Try this R.any example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. any<T>(predicate: (x: T) => boolean, list: T[]): boolean;
  3. any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
  4. ```



R.any source

  1. ```javascript
  2. export function any(predicate, list){
  3.   if (arguments.length === 1) return _list => any(predicate, _list)

  4.   let counter = 0
  5.   while (counter < list.length){
  6.     if (predicate(list[ counter ], counter)){
  7.       return true
  8.     }
  9.     counter++
  10.   }

  11.   return false
  12. }
  13. ```



Tests

  1. ```javascript
  2. import { any } from './any.js'

  3. const list = [ 1, 2, 3 ]

  4. test('happy', () => {
  5.   expect(any(x => x < 0, list)).toBeFalse()
  6. })

  7. test('with curry', () => {
  8.   expect(any(x => x > 2)(list)).toBeTrue()
  9. })
  10. ```



Typescript test

  1. ```typescript
  2. import {any} from 'rambda'

  3. describe('R.any', () => {
  4.   it('happy', () => {
  5.     const result = any(
  6.       x => {
  7.         x // $ExpectType number
  8.         return x > 2
  9.       },
  10.       [1, 2, 3]
  11.     )
  12.     result // $ExpectType boolean
  13.   })

  14.   it('when curried needs a type', () => {
  15.     const result = any<number>(x => {
  16.       x // $ExpectType number
  17.       return x > 2
  18.     })([1, 2, 3])
  19.     result // $ExpectType boolean
  20.   })
  21. })
  22. ```


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

anyPass


  1. ```typescript

  2. anyPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean
  3. ```

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.

Try this R.anyPass example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. anyPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
  3. anyPass<T>(predicates: ((...inputs: T[]) => boolean)[]): (...inputs: T[]) => boolean;
  4. ```



R.anyPass source

  1. ```javascript
  2. export function anyPass(predicates){
  3.   return (...input) => {
  4.     let counter = 0
  5.     while (counter < predicates.length){
  6.       if (predicates[ counter ](...input)){
  7.         return true
  8.       }
  9.       counter++
  10.     }

  11.     return false
  12.   }
  13. }
  14. ```



Tests

  1. ```javascript
  2. import { anyPass } from './anyPass.js'

  3. test('happy', () => {
  4.   const rules = [ x => typeof x === 'string', x => x > 10 ]
  5.   const predicate = anyPass(rules)
  6.   expect(predicate('foo')).toBeTrue()
  7.   expect(predicate(6)).toBeFalse()
  8. })

  9. test('happy', () => {
  10.   const rules = [ x => typeof x === 'string', x => x > 10 ]

  11.   expect(anyPass(rules)(11)).toBeTrue()
  12.   expect(anyPass(rules)(undefined)).toBeFalse()
  13. })

  14. const obj = {
  15.   a : 1,
  16.   b : 2,
  17. }

  18. test('when returns true', () => {
  19.   const conditionArr = [ val => val.a === 1, val => val.a === 2 ]

  20.   expect(anyPass(conditionArr)(obj)).toBeTrue()
  21. })

  22. test('when returns false + curry', () => {
  23.   const conditionArr = [ val => val.a === 2, val => val.b === 3 ]

  24.   expect(anyPass(conditionArr)(obj)).toBeFalse()
  25. })

  26. test('with empty predicates list', () => {
  27.   expect(anyPass([])(3)).toBeFalse()
  28. })

  29. test('works with multiple inputs', () => {
  30.   const fn = function (
  31.     w, x, y, z
  32.   ){
  33.     console.log(
  34.       w, x, y, z
  35.     )

  36.     return w + x === y + z
  37.   }
  38.   expect(anyPass([ fn ])(
  39.     3, 3, 3, 3
  40.   )).toBeTrue()
  41. })
  42. ```



Typescript test

  1. ```typescript
  2. import {anyPass, filter} from 'rambda'

  3. describe('anyPass', () => {
  4.   it('happy', () => {
  5.     const x = anyPass<number>([
  6.       y => {
  7.         y // $ExpectType number
  8.         return typeof y === 'number'
  9.       },
  10.       y => {
  11.         return y > 0
  12.       },
  13.     ])(11)

  14.     x // $ExpectType boolean
  15.   })
  16.   it('issue #604', () => {
  17.     const plusEq = function(w: number, x: number, y: number, z: number) {
  18.       return w + x === y + z
  19.     }
  20.     const result = anyPass([plusEq])(3, 3, 3, 3)

  21.     result // $ExpectType boolean
  22.   })
  23.   it('issue #642', () => {
  24.     const isGreater = (num: number) => num > 5
  25.     const pred = anyPass([isGreater])
  26.     const xs = [0, 1, 2, 3]

  27.     const filtered1 = filter(pred)(xs)
  28.     filtered1 // $ExpectType number[]
  29.     const filtered2 = xs.filter(pred)
  30.     filtered2 // $ExpectType number[]
  31.   })
  32. })
  33. ```


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

append


  1. ```typescript

  2. append<T>(x: T, list: T[]): T[]
  3. ```

It adds element x at the end of list.

Try this R.append example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. append<T>(x: T, list: T[]): T[];
  3. append<T>(x: T): <T>(list: T[]) => T[];
  4. ```



R.append source

  1. ```javascript
  2. import { cloneList } from './_internals/cloneList.js'

  3. export function append(x, input){
  4.   if (arguments.length === 1) return _input => append(x, _input)

  5.   if (typeof input === 'string') return input.split('').concat(x)

  6.   const clone = cloneList(input)
  7.   clone.push(x)

  8.   return clone
  9. }
  10. ```



Tests

  1. ```javascript
  2. import { append } from './append.js'

  3. test('happy', () => {
  4.   expect(append('tests', [ 'write', 'more' ])).toEqual([
  5.     'write',
  6.     'more',
  7.     'tests',
  8.   ])
  9. })

  10. test('append to empty array', () => {
  11.   expect(append('tests')([])).toEqual([ 'tests' ])
  12. })

  13. test('with strings', () => {
  14.   expect(append('o', 'fo')).toEqual([ 'f', 'o', 'o' ])
  15. })
  16. ```



Typescript test

  1. ```typescript
  2. import {append} from 'rambda'

  3. const list = [1, 2, 3]

  4. describe('R.append', () => {
  5.   it('happy', () => {
  6.     const result = append(4, list)

  7.     result // $ExpectType number[]
  8.   })
  9.   it('curried', () => {
  10.     const result = append(4)(list)

  11.     result // $ExpectType number[]
  12.   })
  13. })
  14. ```


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

apply


  1. ```typescript

  2. apply<T = any>(fn: (...args: any[]) => T, args: any[]): T
  3. ```

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.

Try this R.apply example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. apply<T = any>(fn: (...args: any[]) => T, args: any[]): T;
  3. apply<T = any>(fn: (...args: any[]) => T): (args: any[]) => T;
  4. ```



R.apply source

  1. ```javascript
  2. export function apply(fn, args){
  3.   if (arguments.length === 1){
  4.     return _args => apply(fn, _args)
  5.   }

  6.   return fn.apply(this, args)
  7. }
  8. ```



Tests

  1. ```javascript
  2. import { apply } from './apply.js'
  3. import { bind } from './bind.js'
  4. import { identity } from './identity.js'

  5. test('happy', () => {
  6.   expect(apply(identity, [ 1, 2, 3 ])).toBe(1)
  7. })

  8. test('applies function to argument list', () => {
  9.   expect(apply(Math.max, [ 1, 2, 3, -99, 42, 6, 7 ])).toBe(42)
  10. })

  11. test('provides no way to specify context', () => {
  12.   const obj = {
  13.     method : function (){
  14.       return this === obj
  15.     },
  16.   }
  17.   expect(apply(obj.method, [])).toBeFalse()
  18.   expect(apply(bind(obj.method, obj), [])).toBeTrue()
  19. })
  20. ```



Typescript test

  1. ```typescript
  2. import {apply, identity} from 'rambda'

  3. describe('R.apply', () => {
  4.   it('happy', () => {
  5.     const result = apply<number>(identity, [1, 2, 3])

  6.     result // $ExpectType number
  7.   })
  8.   it('curried', () => {
  9.     const fn = apply<number>(identity)
  10.     const result = fn([1, 2, 3])

  11.     result // $ExpectType number
  12.   })
  13. })
  14. ```


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

applySpec


  1. ```typescript

  2. applySpec<Spec extends Record<string, AnyFunction>>(
  3.   spec: Spec
  4. ): (
  5.   ...args: Parameters<ValueOfRecord<Spec>>
  6. ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
  7. ```

Try this R.applySpec example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. applySpec<Spec extends Record<string, AnyFunction>>(
  3.   spec: Spec
  4. ): (
  5.   ...args: Parameters<ValueOfRecord<Spec>>
  6. ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> };
  7. applySpec<T>(spec: any): (...args: unknown[]) => T;
  8. ```



R.applySpec source

  1. ```javascript
  2. import { isArray } from './_internals/isArray.js'

  3. // recursively traverse the given spec object to find the highest arity function
  4. export function __findHighestArity(spec, max = 0){
  5.   for (const key in spec){
  6.     if (spec.hasOwnProperty(key) === false || key === 'constructor') continue

  7.     if (typeof spec[ key ] === 'object'){
  8.       max = Math.max(max, __findHighestArity(spec[ key ]))
  9.     }

  10.     if (typeof spec[ key ] === 'function'){
  11.       max = Math.max(max, spec[ key ].length)
  12.     }
  13.   }

  14.   return max
  15. }

  16. function __filterUndefined(){
  17.   const defined = []
  18.   let i = 0
  19.   const l = arguments.length
  20.   while (i < l){
  21.     if (typeof arguments[ i ] === 'undefined') break
  22.     defined[ i ] = arguments[ i ]
  23.     i++
  24.   }

  25.   return defined
  26. }

  27. function __applySpecWithArity(
  28.   spec, arity, cache
  29. ){
  30.   const remaining = arity - cache.length

  31.   if (remaining === 1)
  32.     return x =>
  33.       __applySpecWithArity(
  34.         spec, arity, __filterUndefined(...cache, x)
  35.       )
  36.   if (remaining === 2)
  37.     return (x, y) =>
  38.       __applySpecWithArity(
  39.         spec, arity, __filterUndefined(
  40.           ...cache, x, y
  41.         )
  42.       )
  43.   if (remaining === 3)
  44.     return (
  45.       x, y, z
  46.     ) =>
  47.       __applySpecWithArity(
  48.         spec, arity, __filterUndefined(
  49.           ...cache, x, y, z
  50.         )
  51.       )
  52.   if (remaining === 4)
  53.     return (
  54.       x, y, z, a
  55.     ) =>
  56.       __applySpecWithArity(
  57.         spec,
  58.         arity,
  59.         __filterUndefined(
  60.           ...cache, x, y, z, a
  61.         )
  62.       )
  63.   if (remaining > 4)
  64.     return (...args) =>
  65.       __applySpecWithArity(
  66.         spec, arity, __filterUndefined(...cache, ...args)
  67.       )

  68.   // handle spec as Array
  69.   if (isArray(spec)){
  70.     const ret = []
  71.     let i = 0
  72.     const l = spec.length
  73.     for (; i < l; i++){
  74.       // handle recursive spec inside array
  75.       if (typeof spec[ i ] === 'object' || isArray(spec[ i ])){
  76.         ret[ i ] = __applySpecWithArity(
  77.           spec[ i ], arity, cache
  78.         )
  79.       }
  80.       // apply spec to the key
  81.       if (typeof spec[ i ] === 'function'){
  82.         ret[ i ] = spec[ i ](...cache)
  83.       }
  84.     }

  85.     return ret
  86.   }

  87.   // handle spec as Object
  88.   const ret = {}
  89.   // apply callbacks to each property in the spec object
  90.   for (const key in spec){
  91.     if (spec.hasOwnProperty(key) === false || key === 'constructor') continue

  92.     // apply the spec recursively
  93.     if (typeof spec[ key ] === 'object'){
  94.       ret[ key ] = __applySpecWithArity(
  95.         spec[ key ], arity, cache
  96.       )
  97.       continue
  98.     }

  99.     // apply spec to the key
  100.     if (typeof spec[ key ] === 'function'){
  101.       ret[ key ] = spec[ key ](...cache)
  102.     }
  103.   }

  104.   return ret
  105. }

  106. export function applySpec(spec, ...args){
  107.   // get the highest arity spec function, cache the result and pass to __applySpecWithArity
  108.   const arity = __findHighestArity(spec)

  109.   if (arity === 0){
  110.     return () => ({})
  111.   }
  112.   const toReturn = __applySpecWithArity(
  113.     spec, arity, args
  114.   )

  115.   return toReturn
  116. }
  117. ```



Tests

  1. ```javascript
  2. import { applySpec as applySpecRamda, nAry } from 'ramda'

  3. import {
  4.   add,
  5.   always,
  6.   compose,
  7.   dec,
  8.   inc,
  9.   map,
  10.   path,
  11.   prop,
  12.   T,
  13. } from '../rambda.js'
  14. import { applySpec } from './applySpec.js'

  15. test('different than Ramda when bad spec', () => {
  16.   const result = applySpec({ sum : { a : 1 } })(1, 2)
  17.   const ramdaResult = applySpecRamda({ sum : { a : 1 } })(1, 2)
  18.   expect(result).toEqual({})
  19.   expect(ramdaResult).toEqual({ sum : { a : {} } })
  20. })

  21. test('works with empty spec', () => {
  22.   expect(applySpec({})()).toEqual({})
  23.   expect(applySpec([])(1, 2)).toEqual({})
  24.   expect(applySpec(null)(1, 2)).toEqual({})
  25. })

  26. test('works with unary functions', () => {
  27.   const result = applySpec({
  28.     v : inc,
  29.     u : dec,
  30.   })(1)
  31.   const expected = {
  32.     v : 2,
  33.     u : 0,
  34.   }
  35.   expect(result).toEqual(expected)
  36. })

  37. test('works with binary functions', () => {
  38.   const result = applySpec({ sum : add })(1, 2)
  39.   expect(result).toEqual({ sum : 3 })
  40. })

  41. test('works with nested specs', () => {
  42.   const result = applySpec({
  43.     unnested : always(0),
  44.     nested   : { sum : add },
  45.   })(1, 2)
  46.   const expected = {
  47.     unnested : 0,
  48.     nested   : { sum : 3 },
  49.   }
  50.   expect(result).toEqual(expected)
  51. })

  52. test('works with arrays of nested specs', () => {
  53.   const result = applySpec({
  54.     unnested : always(0),
  55.     nested   : [ { sum : add } ],
  56.   })(1, 2)

  57.   expect(result).toEqual({
  58.     unnested : 0,
  59.     nested   : [ { sum : 3 } ],
  60.   })
  61. })

  62. test('works with arrays of spec objects', () => {
  63.   const result = applySpec([ { sum : add } ])(1, 2)

  64.   expect(result).toEqual([ { sum : 3 } ])
  65. })

  66. test('works with arrays of functions', () => {
  67.   const result = applySpec([ map(prop('a')), map(prop('b')) ])([
  68.     {
  69.       a : 'a1',
  70.       b : 'b1',
  71.     },
  72.     {
  73.       a : 'a2',
  74.       b : 'b2',
  75.     },
  76.   ])
  77.   const expected = [
  78.     [ 'a1', 'a2' ],
  79.     [ 'b1', 'b2' ],
  80.   ]
  81.   expect(result).toEqual(expected)
  82. })

  83. test('works with a spec defining a map key', () => {
  84.   expect(applySpec({ map : prop('a') })({ a : 1 })).toEqual({ map : 1 })
  85. })

  86. test('cannot retains the highest arity', () => {
  87.   const f = applySpec({
  88.     f1 : nAry(2, T),
  89.     f2 : nAry(5, T),
  90.   })
  91.   const fRamda = applySpecRamda({
  92.     f1 : nAry(2, T),
  93.     f2 : nAry(5, T),
  94.   })
  95.   expect(f).toHaveLength(0)
  96.   expect(fRamda).toHaveLength(5)
  97. })

  98. test('returns a curried function', () => {
  99.   expect(applySpec({ sum : add })(1)(2)).toEqual({ sum : 3 })
  100. })

  101. // Additional tests
  102. // ============================================
  103. test('arity', () => {
  104.   const spec = {
  105.     one   : x1 => x1,
  106.     two   : (x1, x2) => x1 + x2,
  107.     three : (
  108.       x1, x2, x3
  109.     ) => x1 + x2 + x3,
  110.   }
  111.   expect(applySpec(
  112.     spec, 1, 2, 3
  113.   )).toEqual({
  114.     one   : 1,
  115.     two   : 3,
  116.     three : 6,
  117.   })
  118. })

  119. test('arity over 5 arguments', () => {
  120.   const spec = {
  121.     one   : x1 => x1,
  122.     two   : (x1, x2) => x1 + x2,
  123.     three : (
  124.       x1, x2, x3
  125.     ) => x1 + x2 + x3,
  126.     four : (
  127.       x1, x2, x3, x4
  128.     ) => x1 + x2 + x3 + x4,
  129.     five : (
  130.       x1, x2, x3, x4, x5
  131.     ) => x1 + x2 + x3 + x4 + x5,
  132.   }
  133.   expect(applySpec(
  134.     spec, 1, 2, 3, 4, 5
  135.   )).toEqual({
  136.     one   : 1,
  137.     two   : 3,
  138.     three : 6,
  139.     four  : 10,
  140.     five  : 15,
  141.   })
  142. })

  143. test('curried', () => {
  144.   const spec = {
  145.     one   : x1 => x1,
  146.     two   : (x1, x2) => x1 + x2,
  147.     three : (
  148.       x1, x2, x3
  149.     ) => x1 + x2 + x3,
  150.   }
  151.   expect(applySpec(spec)(1)(2)(3)).toEqual({
  152.     one   : 1,
  153.     two   : 3,
  154.     three : 6,
  155.   })
  156. })

  157. test('curried over 5 arguments', () => {
  158.   const spec = {
  159.     one   : x1 => x1,
  160.     two   : (x1, x2) => x1 + x2,
  161.     three : (
  162.       x1, x2, x3
  163.     ) => x1 + x2 + x3,
  164.     four : (
  165.       x1, x2, x3, x4
  166.     ) => x1 + x2 + x3 + x4,
  167.     five : (
  168.       x1, x2, x3, x4, x5
  169.     ) => x1 + x2 + x3 + x4 + x5,
  170.   }
  171.   expect(applySpec(spec)(1)(2)(3)(4)(5)).toEqual({
  172.     one   : 1,
  173.     two   : 3,
  174.     three : 6,
  175.     four  : 10,
  176.     five  : 15,
  177.   })
  178. })

  179. test('undefined property', () => {
  180.   const spec = { prop : path([ 'property', 'doesnt', 'exist' ]) }
  181.   expect(applySpec(spec, {})).toEqual({ prop : undefined })
  182. })

  183. test('restructure json object', () => {
  184.   const spec = {
  185.     id          : path('user.id'),
  186.     name        : path('user.firstname'),
  187.     profile     : path('user.profile'),
  188.     doesntExist : path('user.profile.doesntExist'),
  189.     info        : { views : compose(inc, prop('views')) },
  190.     type        : always('playa'),
  191.   }

  192.   const data = {
  193.     user : {
  194.       id        : 1337,
  195.       firstname : 'john',
  196.       lastname  : 'shaft',
  197.       profile   : 'shaft69',
  198.     },
  199.     views : 42,
  200.   }

  201.   expect(applySpec(spec, data)).toEqual({
  202.     id          : 1337,
  203.     name        : 'john',
  204.     profile     : 'shaft69',
  205.     doesntExist : undefined,
  206.     info        : { views : 43 },
  207.     type        : 'playa',
  208.   })
  209. })
  210. ```



Typescript test

  1. ```typescript
  2. import {multiply, applySpec, inc, dec, add} from 'rambda'

  3. describe('applySpec', () => {
  4.   it('ramda 1', () => {
  5.     const result = applySpec({
  6.       v: inc,
  7.       u: dec,
  8.     })(1)
  9.     result // $ExpectType { v: number; u: number; }
  10.   })
  11.   it('ramda 1', () => {
  12.     interface Output {
  13.       sum: number,
  14.       multiplied: number,
  15.     }
  16.     const result = applySpec<Output>({
  17.       sum: add,
  18.       multiplied: multiply,
  19.     })(1, 2)

  20.     result // $ExpectType Output
  21.   })
  22. })
  23. ```


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

assoc


It makes a shallow clone of obj with setting or overriding the property prop with newValue.

Try this R.assoc example in Rambda REPL

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

assocPath


  1. ```typescript

  2. assocPath<Output>(path: Path, newValue: any, obj: object): Output
  3. ```

It makes a shallow clone of obj with setting or overriding with newValue the property found with path.

Try this R.assocPath example in Rambda REPL


All Typescript definitions

  1. ```typescript
  2. assocPath<Output>(path: Path, newValue: any, obj: object): Output;
  3. assocPath<Output>(path: Path, newValue: any): (obj: object) => Output;
  4. assocPath<Output>(path: Path): (newValue: any) => (obj: object) => Output;
  5. ```



R.assocPath source

  1. ```javascript
  2. import { cloneList } from './_internals/cloneList.js'
  3. import { isArray } from './_internals/isArray.js'
  4. import { isInteger } from './_internals/isInteger.js'
  5. import { assoc } from './assoc.js'
  6. import { curry } from './curry.js'

  7. function assocPathFn(
  8.   path, newValue, input
  9. ){
  10.   const pathArrValue =
  11.     typeof path === 'string' ?
  12.       path.split('.').map(x => isInteger(Number(x)) ? Number(x) : x) :
  13.       path
  14.   if (pathArrValue.length === 0){
  15.     return newValue
  16.   }

  17.   const index = pathArrValue[ 0 ]
  18.   if (pathArrValue.length > 1){
  19.     const condition =
  20.       typeof input !== 'object' ||
  21.       input === null ||
  22.       !input.hasOwnProperty(index)

  23.     const nextInput = condition ?
  24.       isInteger(pathArrValue[ 1 ]) ?
  25.         [] :
  26.         {} :
  27.       input[ index ]

  28.     newValue = assocPathFn(
  29.       Array.prototype.slice.call(pathArrValue, 1),
  30.       newValue,
  31.       nextInput
  32.     )
  33.   }

  34.   if (isInteger(index) && isArray(input)){
  35.     const arr = cloneList(input)
  36.     arr[ index ] = newValue

  37.     return arr
  38.   }

  39.   return assoc(
  40.     index, newValue, input
  41.   )
  42. }

  43. export const assocPath = curry(assocPathFn)
  44. ```



Tests

  1. ```javascript
  2. import { assocPath } from './assocPath.js'

  3. test('string can be used as path input', () => {
  4.   const testObj = {
  5.     a : [ { b : 1 }, { b : 2 } ],
  6.     d : 3,
  7.   }
  8.   const result = assocPath(
  9.     'a.0.b', 10, testObj
  10.   )
  11.   const expected = {
  12.     a : [ { b : 10 }, { b : 2 } ],
  13.     d : 3,
  14.   }
  15.   expect(result).toEqual(expected)
  16. })

  17. test('bug', () => {
  18.   /*
  19.     https://github.com/selfrefactor/rambda/issues/524
  20.   */
  21.   const state = {}

  22.   const withDateLike = assocPath(
  23.     [ 'outerProp', '2020-03-10' ],
  24.     { prop : 2 },
  25.     state
  26.   )
  27.   const withNumber = assocPath(
  28.     [ 'outerProp', '5' ], { prop : 2 }, state
  29.   )

  30.   const withDateLikeExpected = { outerProp : { '2020-03-10' : { prop : 2 } } }
  31.   const withNumberExpected = { outerProp : { 5 : { prop : 2 } } }
  32.   expect(withDateLike).toEqual(withDateLikeExpected)
  33.   expect(withNumber).toEqual(withNumberExpected)
  34. })

  35. test('adds a key to an empty object', () => {
  36.   expect(assocPath(
  37.     [ 'a' ], 1, {}
  38.   )).toEqual({ a : 1 })
  39. })

  40. test('adds a key to a non-empty object', () => {
  41.   expect(assocPath(
  42.     'b', 2, { a : 1 }
  43.   )).toEqual({
  44.     a : 1,
  45.     b : 2,
  46.   })
  47. })

  48. test('adds a nested key to a non-empty object', () => {
  49.   expect(assocPath(
  50.     'b.c', 2, { a : 1 }
  51.   )).toEqual({
  52.     a : 1,
  53.     b : { c : 2 },
  54.   })
  55. })

  56. test('adds a nested key to a nested non-empty object - curry case 1', () => {
  57.   expect(assocPath('b.d',
  58.     3)({
  59.     a : 1,
  60.     b : { c : 2 },
  61.   })).toEqual({
  62.     a : 1,
  63.     b : {
  64.       c : 2,
  65.       d : 3,
  66.     },
  67.   })
  68. })

  69. test('adds a key to a non-empty object - curry case 1', () => {
  70.   expect(assocPath('b', 2)({ a : 1 })).toEqual({
  71.     a : 1,
  72.     b : 2,
  73.   })
  74. })

  75. test('adds a nested key to a non-empty object - curry case 1', () => {
  76.   expect(assocPath('b.c', 2)({ a : 1 })).toEqual({
  77.     a : 1,
  78.     b : { c : 2 },
  79.   })
  80. })

  81. test('adds a key to a non-empty object - curry case 2', () => {
  82.   expect(assocPath('b')(2, { a : 1 })).toEqual({
  83.     a : 1,
  84.     b : 2,
  85.   })
  86. })

  87. test('adds a key to a non-empty object - curry case 3', () => {
  88.   const result = assocPath('b')(2)({ a : 1 })

  89.   expect(result).toEqual({
  90.     a : 1,
  91.     b : 2,
  92.   })
  93. })

  94. test('changes an existing key', () => {
  95.   expect(assocPath(
  96.     'a', 2, { a : 1 }
  97.   )).toEqual({ a : 2 })
  98. })

  99. test('undefined is considered an empty object', () => {
  100.   expect(assocPath(
  101.     'a', 1, undefined
  102.   )).toEqual({ a : 1 })
  103. })

  104. test('null is considered an empty object', () => {
  105.   expect(assocPath(
  106.     'a', 1, null
  107.   )).toEqual({ a : 1 })
  108. })

  109. test('value can be null', () => {
  110.   expect(assocPath(
  111.     'a', null, null
  112.   )).toEqual({ a : null })
  113. })

  114. test('value can be undefined', () => {
  115.   expect(assocPath(
  116.     'a', undefined, null
  117.   )).toEqual({ a : undefined })
  118. })

  119. test('assignment is shallow', () => {
  120.   expect(assocPath(
  121.     'a', { b : 2 }, { a : { c : 3 } }
  122.   )).toEqual({ a : { b : 2 } })
  123. })

  124. test('empty array as path', () => {
  125.   const result = assocPath(
  126.     [], 3, {
  127.       a : 1,
  128.       b : 2,
  129.     }
  130.   )
  131.   expect(result).toBe(3)
  132. })

  133. test('happy', () => {
  134.   const expected = { foo : { bar : { baz : 42 } } }
  135.   const result = assocPath(
  136.     [ 'foo', 'bar', 'baz' ], 42, { foo : null }
  137.   )
  138.   expect(result).toEqual(expected)
  139. })
  140. ```



Typescript test

  1. ```typescript
  2. import {assocPath} from 'rambda'

  3. interface Output {
  4.   a: number,
  5.   foo: {bar: number},
  6. }

  7. describe('R.assocPath - user must explicitly set type of output', () => {
  8.   it('with array as path input', () => {
  9.     const result = assocPath<Output>(['foo', 'bar'], 2, {a: 1})

  10.     result // $ExpectType Output
  11.   })
  12.   it('with string as path input', () => {
  13.     const result = assocPath<Output>('foo.bar', 2, {a: 1})

  14.     result // $ExpectType Output
  15.   })
  16. })

  17. describe('R.assocPath - curried', () => {
  18.   it('with array as path input', () => {
  19.     const result = assocPath<Output>(['foo', 'bar'], 2)({a: 1})

  20.     result // $ExpectType Output
  21.   })
  22.   it('with string as path input', () => {
  23.     const result = assocPath<Output>('foo.bar', 2)({a: 1})

  24.     result // $ExpectType Output
  25.   })
  26. })
  27. ```


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

bind


  1. ```typescript

  2. bind<F extends AnyFunction, T>(fn: F, thisObj: T): (...args: Parameters<F>) => ReturnType<F>
  3. ```