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
- ```javascript
- import { compose, map, filter } from 'rambda'
- const result = compose(
- map(x => x * 2),
- filter(x => x > 2)
- )([1, 2, 3, 4])
- // => [6, 8]
- ```
You can test this example in Rambda's REPL
❯ Rambda's advantages
Typescript included
Typescript definitions are included in the library, in comparison to Ramda, where you need to additionally install @types/ramda.
Still, you need to be aware that functional programming features in Typescript are in development, which means that using R.compose/R.pipe can be problematic.
Important - Rambda version 7.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.
- ```
- import {add} from 'rambda/immutable'
- ```
Deno support
While Ramda is available for Deno users, Rambda provides you with included TS definitions:
- ```
- import * as R from "https://x.nest.land/rambda@7.1.0/mod.ts";
- import * as Ramda from "https://x.nest.land/ramda@0.28.0/mod.ts";
- R.add(1)('foo') // => will trigger warning in VSCode
- Ramda.add(1)('foo') // => will not trigger warning in VSCode
- ```
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):
- ```
- 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
- ```
Speed
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:
- ```
- https://unpkg.com/rambda@CURRENT_VERSION/dist/rambda.umd.js
- ```
- with deno
- ```
- import {compose, add} from 'https://raw.githubusercontent.com/selfrefactor/rambda/master/dist/rambda.esm.js'
- ```
Differences between Rambda and Ramda
- Rambda's type detects async functions and unresolved Promises. The returned values are 'Async' and 'Promise'.
- Rambda's type handles NaN input, in which case it returns NaN.
- Rambda's forEach can iterate over objects not only arrays.
- Rambda's map, filter, partition when they iterate over objects, they pass property and input object as predicate's argument.
- Rambda's filter returns empty array with bad input(null 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.
adjust
- ```typescript
- adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[]
- ```
It replaces index in array list with the result of replaceFn(list[i]).
All Typescript definitions
- ```typescript
- adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[];
- adjust<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
- ```
R.adjust source
- ```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)
- ```
Tests
- ```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
- ```typescript
- all<T>(predicate: (x: T) => boolean, list: T[]): boolean
- ```
It returns true, if all members of array list returns true, when applied as argument to predicate function.
All Typescript definitions
- ```typescript
- all<T>(predicate: (x: T) => boolean, list: T[]): boolean;
- all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
- ```
R.all source
- ```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
- }
- ```
Tests
- ```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 test
- ```typescript
- import {all} from 'rambda'
- describe('all', () => {
- it('happy', () => {
- const result = all(
- x => {
- x // $ExpectType number
- return x > 0
- },
- [1, 2, 3]
- )
- result // $ExpectType boolean
- })
- it('curried needs a type', () => {
- const result = all<number>(x => {
- x // $ExpectType number
- return x > 0
- })([1, 2, 3])
- result // $ExpectType boolean
- })
- })
- ```
allPass
- ```typescript
- allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean
- ```
It returns true, if all functions of predicates return true, when input is their argument.
All Typescript definitions
- ```typescript
- allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
- allPass<T>(predicates: ((...inputs: T[]) => boolean)[]): (...inputs: T[]) => boolean;
- ```
R.allPass source
- ```javascript
- export function allPass(predicates){
- return (...input) => {
- let counter = 0
- while (counter < predicates.length){
- if (!predicates[ counter ](...input)){
- return false
- }
- counter++
- }
- return true
- }
- }
- ```
Tests
- ```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 test
- ```typescript
- import {allPass, filter} from 'rambda'
- describe('allPass', () => {
- it('happy', () => {
- const x = allPass<number>([
- y => {
- y // $ExpectType number
- return typeof y === 'number'
- },
- y => {
- return y > 0
- },
- ])(11)
- x // $ExpectType boolean
- })
- it('issue #642', () => {
- const isGreater = (num: number) => num > 5
- const pred = allPass([isGreater])
- const xs = [0, 1, 2, 3]
- const filtered1 = filter(pred)(xs)
- filtered1 // $ExpectType number[]
- const filtered2 = xs.filter(pred)
- filtered2 // $ExpectType number[]
- })
- it('issue #604', () => {
- const plusEq = function(w: number, x: number, y: number, z: number) {
- return w + x === y + z
- }
- const result = allPass([plusEq])(3, 3, 3, 3)
- result // $ExpectType boolean
- })
- })
- ```
always
It returns function that always returns x.
and
Logical AND
any
- ```typescript
- any<T>(predicate: (x: T) => boolean, list: T[]): boolean
- ```
It returns true, if at least one member of list returns true, when passed to a predicate function.
All Typescript definitions
- ```typescript
- any<T>(predicate: (x: T) => boolean, list: T[]): boolean;
- any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
- ```
R.any source
- ```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
- }
- ```
Tests
- ```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 test
- ```typescript
- import {any} from 'rambda'
- describe('R.any', () => {
- it('happy', () => {
- const result = any(
- x => {
- x // $ExpectType number
- return x > 2
- },
- [1, 2, 3]
- )
- result // $ExpectType boolean
- })
- it('when curried needs a type', () => {
- const result = any<number>(x => {
- x // $ExpectType number
- return x > 2
- })([1, 2, 3])
- result // $ExpectType boolean
- })
- })
- ```
anyPass
- ```typescript
- 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.
All Typescript definitions
- ```typescript
- anyPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
- anyPass<T>(predicates: ((...inputs: T[]) => boolean)[]): (...inputs: T[]) => boolean;
- ```
R.anyPass source
- ```javascript
- export function anyPass(predicates){
- return (...input) => {
- let counter = 0
- while (counter < predicates.length){
- if (predicates[ counter ](...input)){
- return true
- }
- counter++
- }
- return false
- }
- }
- ```
Tests
- ```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 test
- ```typescript
- import {anyPass, filter} from 'rambda'
- describe('anyPass', () => {
- it('happy', () => {
- const x = anyPass<number>([
- y => {
- y // $ExpectType number
- return typeof y === 'number'
- },
- y => {
- return y > 0
- },
- ])(11)
- x // $ExpectType boolean
- })
- it('issue #604', () => {
- const plusEq = function(w: number, x: number, y: number, z: number) {
- return w + x === y + z
- }
- const result = anyPass([plusEq])(3, 3, 3, 3)
- result // $ExpectType boolean
- })
- it('issue #642', () => {
- const isGreater = (num: number) => num > 5
- const pred = anyPass([isGreater])
- const xs = [0, 1, 2, 3]
- const filtered1 = filter(pred)(xs)
- filtered1 // $ExpectType number[]
- const filtered2 = xs.filter(pred)
- filtered2 // $ExpectType number[]
- })
- })
- ```
append
- ```typescript
- append<T>(x: T, list: T[]): T[]
- ```
It adds element x at the end of list.
All Typescript definitions
- ```typescript
- append<T>(x: T, list: T[]): T[];
- append<T>(x: T): <T>(list: T[]) => T[];
- ```
R.append source
- ```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
- }
- ```
Tests
- ```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 test
- ```typescript
- import {append} from 'rambda'
- const list = [1, 2, 3]
- describe('R.append', () => {
- it('happy', () => {
- const result = append(4, list)
- result // $ExpectType number[]
- })
- it('curried', () => {
- const result = append(4)(list)
- result // $ExpectType number[]
- })
- })
- ```
apply
- ```typescript
- apply<T = any>(fn: (...args: any[]) => T, args: any[]): T
- ```
It applies function fn to the list of arguments.
This is useful for creating a fixed-arity function from a variadic function. fn should be a bound function if context is significant.
All Typescript definitions
- ```typescript
- apply<T = any>(fn: (...args: any[]) => T, args: any[]): T;
- apply<T = any>(fn: (...args: any[]) => T): (args: any[]) => T;
- ```
R.apply source
- ```javascript
- export function apply(fn, args){
- if (arguments.length === 1){
- return _args => apply(fn, _args)
- }
- return fn.apply(this, args)
- }
- ```
Tests
- ```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 : function (){
- return this === obj
- },
- }
- expect(apply(obj.method, [])).toBeFalse()
- expect(apply(bind(obj.method, obj), [])).toBeTrue()
- })
- ```
Typescript test
- ```typescript
- import {apply, identity} from 'rambda'
- describe('R.apply', () => {
- it('happy', () => {
- const result = apply<number>(identity, [1, 2, 3])
- result // $ExpectType number
- })
- it('curried', () => {
- const fn = apply<number>(identity)
- const result = fn([1, 2, 3])
- result // $ExpectType number
- })
- })
- ```
applySpec
- ```typescript
- applySpec<Spec extends Record<string, AnyFunction>>(
- spec: Spec
- ): (
- ...args: Parameters<ValueOfRecord<Spec>>
- ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
- ```
All Typescript definitions
- ```typescript
- applySpec<Spec extends Record<string, AnyFunction>>(
- spec: Spec
- ): (
- ...args: Parameters<ValueOfRecord<Spec>>
- ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> };
- applySpec<T>(spec: any): (...args: unknown[]) => T;
- ```
R.applySpec source
- ```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
- }
- ```
Tests
- ```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',
- })
- })
- ```
Typescript test
- ```typescript
- import {multiply, applySpec, inc, dec, add} from 'rambda'
- describe('applySpec', () => {
- it('ramda 1', () => {
- const result = applySpec({
- v: inc,
- u: dec,
- })(1)
- result // $ExpectType { v: number; u: number; }
- })
- it('ramda 1', () => {
- interface Output {
- sum: number,
- multiplied: number,
- }
- const result = applySpec<Output>({
- sum: add,
- multiplied: multiply,
- })(1, 2)
- result // $ExpectType Output
- })
- })
- ```
assoc
It makes a shallow clone of obj with setting or overriding the property prop with newValue.
assocPath
- ```typescript
- assocPath<Output>(path: Path, newValue: any, obj: object): Output
- ```
It makes a shallow clone of obj with setting or overriding with newValue the property found with path.
All Typescript definitions
- ```typescript
- assocPath<Output>(path: Path, newValue: any, obj: object): Output;
- assocPath<Output>(path: Path, newValue: any): (obj: object) => Output;
- assocPath<Output>(path: Path): (newValue: any) => (obj: object) => Output;
- ```
R.assocPath source
- ```javascript
- import { cloneList } from './_internals/cloneList.js'
- import { isArray } from './_internals/isArray.js'
- import { isInteger } from './_internals/isInteger.js'
- import { assoc } from './assoc.js'
- import { curry } from './curry.js'
- function assocPathFn(
- path, newValue, input
- ){
- const pathArrValue =
- typeof path === 'string' ?
- path.split('.').map(x => isInteger(Number(x)) ? Number(x) : x) :
- path
- if (pathArrValue.length === 0){
- return newValue
- }
- const index = pathArrValue[ 0 ]
- if (pathArrValue.length > 1){
- const condition =
- typeof input !== 'object' ||
- input === null ||
- !input.hasOwnProperty(index)
- const nextInput = condition ?
- isInteger(pathArrValue[ 1 ]) ?
- [] :
- {} :
- input[ index ]
- newValue = assocPathFn(
- Array.prototype.slice.call(pathArrValue, 1),
- newValue,
- nextInput
- )
- }
- if (isInteger(index) && isArray(input)){
- const arr = cloneList(input)
- arr[ index ] = newValue
- return arr
- }
- return assoc(
- index, newValue, input
- )
- }
- export const assocPath = curry(assocPathFn)
- ```
Tests
- ```javascript
- import { assocPath } from './assocPath.js'
- test('string can be used as path input', () => {
- const testObj = {
- a : [ { b : 1 }, { b : 2 } ],
- d : 3,
- }
- const result = assocPath(
- 'a.0.b', 10, testObj
- )
- const expected = {
- a : [ { b : 10 }, { b : 2 } ],
- d : 3,
- }
- expect(result).toEqual(expected)
- })
- test('bug', () => {
- /*
- https://github.com/selfrefactor/rambda/issues/524
- */
- const state = {}
- const withDateLike = assocPath(
- [ 'outerProp', '2020-03-10' ],
- { prop : 2 },
- state
- )
- const withNumber = assocPath(
- [ 'outerProp', '5' ], { prop : 2 }, state
- )
- const withDateLikeExpected = { outerProp : { '2020-03-10' : { prop : 2 } } }
- const withNumberExpected = { outerProp : { 5 : { prop : 2 } } }
- expect(withDateLike).toEqual(withDateLikeExpected)
- expect(withNumber).toEqual(withNumberExpected)
- })
- test('adds a key to an empty object', () => {
- expect(assocPath(
- [ 'a' ], 1, {}
- )).toEqual({ a : 1 })
- })
- test('adds a key to a non-empty object', () => {
- expect(assocPath(
- 'b', 2, { a : 1 }
- )).toEqual({
- a : 1,
- b : 2,
- })
- })
- test('adds a nested key to a non-empty object', () => {
- expect(assocPath(
- 'b.c', 2, { a : 1 }
- )).toEqual({
- a : 1,
- b : { c : 2 },
- })
- })
- test('adds a nested key to a nested non-empty object - curry case 1', () => {
- expect(assocPath('b.d',
- 3)({
- a : 1,
- b : { c : 2 },
- })).toEqual({
- a : 1,
- b : {
- c : 2,
- d : 3,
- },
- })
- })
- test('adds a key to a non-empty object - curry case 1', () => {
- expect(assocPath('b', 2)({ a : 1 })).toEqual({
- a : 1,
- b : 2,
- })
- })
- test('adds a nested key to a non-empty object - curry case 1', () => {
- expect(assocPath('b.c', 2)({ a : 1 })).toEqual({
- a : 1,
- b : { c : 2 },
- })
- })
- test('adds a key to a non-empty object - curry case 2', () => {
- expect(assocPath('b')(2, { a : 1 })).toEqual({
- a : 1,
- b : 2,
- })
- })
- test('adds a key to a non-empty object - curry case 3', () => {
- const result = assocPath('b')(2)({ a : 1 })
- expect(result).toEqual({
- a : 1,
- b : 2,
- })
- })
- test('changes an existing key', () => {
- expect(assocPath(
- 'a', 2, { a : 1 }
- )).toEqual({ a : 2 })
- })
- test('undefined is considered an empty object', () => {
- expect(assocPath(
- 'a', 1, undefined
- )).toEqual({ a : 1 })
- })
- test('null is considered an empty object', () => {
- expect(assocPath(
- 'a', 1, null
- )).toEqual({ a : 1 })
- })
- test('value can be null', () => {
- expect(assocPath(
- 'a', null, null
- )).toEqual({ a : null })
- })
- test('value can be undefined', () => {
- expect(assocPath(
- 'a', undefined, null
- )).toEqual({ a : undefined })
- })
- test('assignment is shallow', () => {
- expect(assocPath(
- 'a', { b : 2 }, { a : { c : 3 } }
- )).toEqual({ a : { b : 2 } })
- })
- test('empty array as path', () => {
- const result = assocPath(
- [], 3, {
- a : 1,
- b : 2,
- }
- )
- expect(result).toBe(3)
- })
- test('happy', () => {
- const expected = { foo : { bar : { baz : 42 } } }
- const result = assocPath(
- [ 'foo', 'bar', 'baz' ], 42, { foo : null }
- )
- expect(result).toEqual(expected)
- })
- ```
Typescript test
- ```typescript
- import {assocPath} from 'rambda'
- interface Output {
- a: number,
- foo: {bar: number},
- }
- describe('R.assocPath - user must explicitly set type of output', () => {
- it('with array as path input', () => {
- const result = assocPath<Output>(['foo', 'bar'], 2, {a: 1})
- result // $ExpectType Output
- })
- it('with string as path input', () => {
- const result = assocPath<Output>('foo.bar', 2, {a: 1})
- result // $ExpectType Output
- })
- })
- describe('R.assocPath - curried', () => {
- it('with array as path input', () => {
- const result = assocPath<Output>(['foo', 'bar'], 2)({a: 1})
- result // $ExpectType Output
- })
- it('with string as path input', () => {
- const result = assocPath<Output>('foo.bar', 2)({a: 1})
- result // $ExpectType Output
- })
- })
- ```
bind
- ```typescript
- bind<F extends AnyFunction, T>(fn: F, thisObj: T): (...args: Parameters<F>) => ReturnType<F>
- ```