rambda
Version:
Lightweight faster alternative to Ramda
2,241 lines (1,739 loc) • 388 kB
Markdown
# Rambda
`Rambda` is faster and smaller alternative to the popular functional programming library **Ramda**. - [Documentation](https://selfrefactor.github.io/rambda/#/)
[](https://circleci.com/gh/selfrefactor/rambda/tree/master)
[](https://codecov.io/gh/selfrefactor/rambda)
[](https://david-dm.org/selfrefactor/rambda)


## 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 <a href="https://rambda.now.sh?const%20result%20%3D%20R.compose(%0A%20%20R.map(x%20%3D%3E%20x%20*%202)%2C%0A%20%20R.filter(x%20%3D%3E%20x%20%3E%202)%0A)(%5B1%2C%202%2C%203%2C%204%5D)%0A%0A%2F%2F%20%3D%3E%20%5B6%2C%208%5D">Rambda's REPL</a>
* [Install](#install)
* [Differences between Rambda and Ramda](#differences-between-rambda-and-ramda)
* [API](#api)
* [Changelog](#changelog)
## Rambda's advantages
- Tree-shaking
Currently **Rambda** is more tree-shakable than **Ramda**
- Speed
**Rambda** is generally more performant than `Ramda` as the [benchmarks](#benchmarks) can prove that.
- dot notation for `R.path` and `R.paths`
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
```
- Typescript included
Typescript definitions are included in the library, in comparison to **Ramda**, where you need to additionally install `@types/ramda`.
- More generic methods
`Ramda` has an overwhelming list of methods, as one could get lost putting all these methods in one's head. `Rambda` has smaller method counts and that could be seen as advantage.
<details>
<summary>
Click to see the full list of 118 Ramda methods not implemented in Rambda
</summary>
- __
- addIndex
- ap
- aperture
- apply
- applyTo
- ascend
- binary
- bind
- call
- comparator
- composeK
- composeP
- composeWith
- construct
- constructN
- contains
- countBy
- descend
- differenceWith
- dissocPath
- dropLastWhile
- dropRepeats
- dropRepeatsWith
- dropWhile
- empty
- eqBy
- eqProps
- evolve
- forEachObjIndexed
- gt
- gte
- hasIn
- hasPath
- innerJoin
- insert
- insertAll
- into
- invert
- invertObj
- invoker
- juxt
- keysIn
- lift
- liftN
- lt
- lte
- mapAccum
- mapAccumRight
- mapObjIndexed
- mathMod
- memoizeWith
- mergeAll
- mergeDeepLeft
- mergeDeepRight
- mergeDeepWith
- mergeDeepWithKey
- mergeLeft
- mergeRight
- mergeWith
- mergeWithKey
- move
- nAry
- nthArg
- o
- objOf
- of
- once
- or
- otherwise
- pair
- partialRight
- partition
- pathEq
- pathSatisfies
- pickBy
- pipeK
- pipeP
- pipeWith
- project
- propSatisfies
- props
- reduceBy
- reduceRight
- reduceWhile
- reduced
- remove
- scan
- sequence
- sortWith
- splitAt
- splitWhen
- symmetricDifferenceWith
- takeLastWhile
- takeWhile
- andThen
- toPairsIn
- transduce
- traverse
- tryCatch
- unapply
- unary
- uncurryN
- unfold
- union
- unionWith
- uniqBy
- unless
- unnest
- until
- useWith
- valuesIn
- where
- whereEq
- xprod
- zipWith
- thunkify
- default
</details>
## Install
- **yarn add rambda**
- For UMD usage either use `./dist/rambda.umd.js` or following CDN link:
```
https://unpkg.com/rambda@CURRENT_VERSION/dist/rambda.umd.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 **path** and **paths** accept dot notation - `'x.y' same as ['x','y']`
- Rambda's **pick** and **omit** accept comma notation - `'x,y' same as ['x','y']`
- Rambda's **map**, **reject** and **forEach** can iterate over objects not only arrays.
- Rambda's **map** and **filter** pass array index as second argument when mapping over arrays.
- Rambda's **adjust**, **all**, **allPass**, **any**, **anyPass**, **findIndex** , **findLastIndex** and **reject** are passing index as second argument to the predicate function.
- Rambda's **filter** returns empty array with bad input(`null` or `undefined`), while Ramda throws.
- Ramda's **includes** will throw an error if input is neither `string` nor `array`, while **Rambda** version will return `false`.
- Ramda's **clamp** work for letters, while Rambda's method work only for numbers.
> If you need more **Ramda** methods in **Rambda**, you may either submit a `PR` or check the extended version of **Rambda** - [Rambdax](https://github.com/selfrefactor/rambdax). In case of the former, you may want to consult with [Rambda contribution guidelines.](CONTRIBUTING.md)
## Benchmarks
<details>
<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.15) and *Ramda*(0.27.0).
</summary>
method | Rambda | Ramda | Lodash
--- |--- | --- | ---
*add* | 96.31% slower | 96.28% slower | 🚀 Fastest
*adjust* | 🚀 Fastest | 3.15% slower | 🔳
*all* | 🚀 Fastest | 94.35% slower | 🔳
*allPass* | 🚀 Fastest | 98.94% slower | 🔳
*any* | 🚀 Fastest | 98.13% slower | 1.44% slower
*anyPass* | 🚀 Fastest | 99.09% slower | 🔳
*append* | 🚀 Fastest | 82.6% slower | 🔳
*applySpec* | 🚀 Fastest | 66.53% slower | 🔳
*assoc* | 86.73% slower | 52.15% slower | 🚀 Fastest
*clone* | 🚀 Fastest | 82.43% slower | 62.59% slower
*compose* | 🚀 Fastest | 95.45% slower | 73.78% slower
*converge* | 44.9% slower | 🚀 Fastest | 🔳
*curry* | 🚀 Fastest | 31.93% slower | 🔳
*curryN* | 55.07% slower | 🚀 Fastest | 🔳
*defaultTo* | 🚀 Fastest | 62.94% slower | 🔳
*drop* | 🚀 Fastest | 97.08% slower | 🔳
*dropLast* | 🚀 Fastest | 96.68% slower | 🔳
*equals* | 76.94% slower | 78.52% slower | 🚀 Fastest
*filter* | 72.76% slower | 87.36% slower | 🚀 Fastest
*find* | 🚀 Fastest | 91.33% slower | 51.26% slower
*findIndex* | 🚀 Fastest | 97.9% slower | 76.76% slower
*flatten* | 95.68% slower | 94.49% slower | 🚀 Fastest
*ifElse* | 🚀 Fastest | 63.06% slower | 🔳
*includes* | 🚀 Fastest | 68.05% slower | 🔳
*indexOf* | 7.01% slower | 85.46% slower | 🚀 Fastest
*init* | 94.02% slower | 96.66% slower | 🚀 Fastest
*is* | 🚀 Fastest | 12.21% slower | 🔳
*isEmpty* | 62.71% slower | 93.35% slower | 🚀 Fastest
*last* | 3.21% slower | 99.62% slower | 🚀 Fastest
*lastIndexOf* | 🚀 Fastest | 41.73% slower | 🔳
*map* | 30.91% slower | 63.2% slower | 🚀 Fastest
*match* | 🚀 Fastest | 41.44% slower | 🔳
*merge* | 62.29% slower | 🚀 Fastest | 52.15% slower
*none* | 🚀 Fastest | 87.49% slower | 🔳
*omit* | 🚀 Fastest | 71.15% slower | 97.62% slower
*over* | 🚀 Fastest | 57.7% slower | 🔳
*path* | 6.33% slower | 74.51% slower | 🚀 Fastest
*pick* | 🚀 Fastest | 22.61% slower | 85.66% slower
*prop* | 🚀 Fastest | 88.53% slower | 🔳
*propEq* | 🚀 Fastest | 93.89% slower | 🔳
*range* | 95.33% slower | 90.36% slower | 🚀 Fastest
*reduce* | 56.8% slower | 72.82% slower | 🚀 Fastest
*repeat* | 84.33% slower | 95.02% slower | 🚀 Fastest
*replace* | 1.27% slower | 31.5% slower | 🚀 Fastest
*set* | 🚀 Fastest | 47.8% slower | 🔳
*sort* | 🚀 Fastest | 62.07% slower | 🔳
*sortBy* | 🚀 Fastest | 59.7% slower | 87.81% slower
*split* | 🚀 Fastest | 85% slower | 32.38% slower
*splitEvery* | 🚀 Fastest | 89.27% slower | 🔳
*take* | 92.41% slower | 97.76% slower | 🚀 Fastest
*takeLast* | 92.71% slower | 98.79% slower | 🚀 Fastest
*test* | 🚀 Fastest | 93.95% slower | 🔳
*type* | 38.93% slower | 🚀 Fastest | 🔳
*uniq* | 99.25% slower | 96.57% slower | 🚀 Fastest
*update* | 🚀 Fastest | 86.54% slower | 🔳
*view* | 🚀 Fastest | 77.47% slower | 🔳
</details>
## Used by
- [WatermelonDB](https://github.com/Nozbe/WatermelonDB)
- [SAP's Cloud SDK](https://github.com/SAP/cloud-sdk)
- [VSCode Slack intergration](https://github.com/verydanny/vcslack)
- [Webpack PostCSS](https://github.com/sectsect/webpack-postcss)
- [MobX-State-Tree decorators](https://github.com/farwayer/mst-decorators)
- [Mobx decorators](https://github.com/farwayer/mobx-decorators)
## API
### add
```typescript
add(a: number, b: number): number
```
It adds `a` and `b`.
```javascript
R.add(2, 3) // => 5
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.add(2%2C%203)%20%2F%2F%20%3D%3E%20%205">Try the above <strong>R.add</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
add(a: number, b: number): number;
add(a: number): (b: number) => number;
```
</details>
<details>
<summary><strong>R.add</strong> source</summary>
```javascript
export function add(a, b){
if (arguments.length === 1) return _b => add(a, _b)
return Number(a) + Number(b)
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { add } from './add'
test('with number', () => {
expect(add(2, 3)).toEqual(5)
expect(add(7)(10)).toEqual(17)
})
test('string is bad input', () => {
expect(add('foo', 'bar')).toBeNaN()
})
test('ramda specs', () => {
expect(add('1', '2')).toEqual(3)
expect(add(1, '2')).toEqual(3)
expect(add(true, false)).toEqual(1)
expect(add(null, null)).toEqual(0)
expect(add(undefined, undefined)).toEqual(NaN)
expect(add(new Date(1), new Date(2))).toEqual(3)
})
```
</details>
<details>
<summary><strong>Typescript</strong> test</summary>
```typescript
import {add} from 'rambda'
describe('add', () => {
it('number', () => {
const result = [
add(4)(1),
add(4,1)
]
result[0] // $ExpectType number
result[1] // $ExpectType number
})
})
```
</details>
### adjust
```typescript
adjust<T>(index: number, replaceFn: (a: T) => T, list: ReadonlyArray<T>): T[]
```
It replaces `index` in array `list` with the result of `replaceFn(list[i])`.
```javascript
R.adjust(
0,
a => a + 1,
[0, 100]
) // => [1, 100]
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.adjust(%0A%20%200%2C%0A%20%20a%20%3D%3E%20a%20%2B%201%2C%0A%20%20%5B0%2C%20100%5D%0A)%20%2F%2F%20%3D%3E%20%5B1%2C%20100%5D">Try the above <strong>R.adjust</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
adjust<T>(index: number, replaceFn: (a: T) => T, list: ReadonlyArray<T>): T[];
adjust<T>(index: number, replaceFn: (a: T) => T): (list: ReadonlyArray<T>) => T[];
```
</details>
<details>
<summary><strong>R.adjust</strong> source</summary>
```javascript
import { curry } from './curry'
function adjustFn(
index, replaceFn, list
){
const actualIndex = index < 0 ? list.length + index : index
if (index >= list.length || actualIndex < 0) return list
const clone = list.slice()
clone[ actualIndex ] = replaceFn(clone[ actualIndex ])
return clone
}
export const adjust = curry(adjustFn)
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { add } from './add'
import { adjust } from './adjust'
const expected = [ 0, 11, 2 ]
test('without curring', () => {
expect(adjust(
1, add(10), [ 0, 1, 2 ]
)).toEqual(expected)
})
test('with curring type 1 1 1', () => {
expect(adjust(1)(add(10))([ 0, 1, 2 ])).toEqual(expected)
})
test('with curring type 1 2', () => {
expect(adjust(1)(add(10), [ 0, 1, 2 ])).toEqual(expected)
})
test('with curring type 2 1', () => {
expect(adjust(1, add(10))([ 0, 1, 2 ])).toEqual(expected)
})
test('with negative index', () => {
expect(adjust(
-2, add(10), [ 0, 1, 2 ]
)).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)
})
```
</details>
<details>
<summary>1 failed <italic>Ramda.adjust</italic> specs
> Reason for the failure: ramda accepts an array-like object
</summary>
```javascript
var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
describe('adjust', function() {
it('accepts an array-like object', function() {
function args() {
return arguments;
}
eq(R.adjust(2, R.add(1), args(0, 1, 2, 3)), [0, 1, 3, 3]);
});
});
```
</details>
### all
```typescript
all<T>(predicate: (x: T) => boolean, list: ReadonlyArray<T>): boolean
```
It returns `true`, if all members of array `list` returns `true`, when applied as argument to `predicate` function.
```javascript
const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > -1
const result = R.all(predicate, arr)
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20list%20%3D%20%5B%200%2C%201%2C%202%2C%203%2C%204%20%5D%0Aconst%20predicate%20%3D%20x%20%3D%3E%20x%20%3E%20-1%0A%0Aconst%20result%20%3D%20R.all(predicate%2C%20arr)%0A%2F%2F%20%3D%3E%20true">Try the above <strong>R.all</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
all<T>(predicate: (x: T) => boolean, list: ReadonlyArray<T>): boolean;
all<T>(predicate: (x: T) => boolean): (list: ReadonlyArray<T>) => boolean;
```
</details>
<details>
<summary><strong>R.all</strong> 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 ], i)) return false
}
return true
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { all } from './all'
const numArr = [ 0, 1, 2, 3, 4 ]
test('when true', () => {
const fn = x => x > -1
expect(all(fn)(numArr)).toBeTrue()
})
test('when false', () => {
const fn = x => x > 2
expect(all(fn, numArr)).toBeFalse()
})
test('pass index as second argument', () => {
const indexes = []
const fn = (x, i) => {
indexes.push(i)
return x > 5
}
all(fn, [ 10, 12, 14 ])
expect(indexes).toEqual([ 0, 1, 2 ])
})
```
</details>
<details>
<summary><strong>Typescript</strong> test</summary>
```typescript
import {all} from 'rambda'
describe('all', () => {
it('happy', () => {
const x = all<number>(y => {
y // $ExpectType number
return y > 0
})([1, 2, 3])
x // $ExpectType boolean
const q = all(y => y > 0, [1, 2, 3]) // $ExpectType boolean
q // $ExpectType boolean
})
})
```
</details>
### 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.
```javascript
const input = {
a : 1,
b : 2,
}
const predicates = [
x => x.a === 1,
x => x.b === 2,
]
const result = R.allPass(predicates)(input) // => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20input%20%3D%20%7B%0A%20%20a%20%3A%201%2C%0A%20%20b%20%3A%202%2C%0A%7D%0Aconst%20predicates%20%3D%20%5B%0A%20%20x%20%3D%3E%20x.a%20%3D%3D%3D%201%2C%0A%20%20x%20%3D%3E%20x.b%20%3D%3D%3D%202%2C%0A%5D%0Aconst%20result%20%3D%20R.allPass(predicates)(input)%20%2F%2F%20%3D%3E%20true">Try the above <strong>R.allPass</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
```
</details>
<details>
<summary><strong>R.allPass</strong> 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
}
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { allPass } from './allPass'
test('happy', () => {
const rules = [ x => typeof x === 'number', x => x > 10, x => x * 7 < 100 ]
expect(allPass(rules)(11)).toBeTrue()
expect(allPass(rules)(undefined)).toBeFalse()
})
test('when returns true', () => {
const conditionArr = [ val => val.a === 1, val => val.b === 2 ]
expect(allPass(conditionArr)({
a : 1,
b : 2,
})).toBeTrue()
})
test('when returns false', () => {
const conditionArr = [ val => val.a === 1, val => val.b === 3 ]
expect(allPass(conditionArr)({
a : 1,
b : 2,
})).toBeFalse()
})
```
</details>
<details>
<summary><strong>Typescript</strong> test</summary>
```typescript
import {allPass} 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
})
})
```
</details>
<details>
<summary>1 failed <italic>Ramda.allPass</italic> specs
> Reason for the failure: ramda returns a curried function whose arity matches that of the highest-arity predicate
</summary>
```javascript
var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
describe('allPass', function() {
var odd = function(n) { return n % 2 !== 0; };
var lt20 = function(n) { return n < 20; };
var gt5 = function(n) { return n > 5; };
var plusEq = function(w, x, y, z) { return w + x === y + z; };
it('returns a curried function whose arity matches that of the highest-arity predicate', function() {
eq(R.allPass([odd, gt5, plusEq]).length, 4);
eq(R.allPass([odd, gt5, plusEq])(9, 9, 9, 9), true);
eq(R.allPass([odd, gt5, plusEq])(9)(9)(9)(9), true);
});
});
```
</details>
### always
```typescript
always<T>(x: T): () => T
```
It returns function that always returns `x`.
```javascript
const fn = R.always(7)
console.log(fn())// => 7
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20fn%20%3D%20R.always(7)%0A%0Aconsole.log(fn())%2F%2F%20%3D%3E%207">Try the above <strong>R.always</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
always<T>(x: T): () => T;
```
</details>
<details>
<summary><strong>R.always</strong> source</summary>
```javascript
export function always(x){
return () => x
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { always } from './always'
test('happy', () => {
const fn = always(7)
expect(fn()).toEqual(7)
expect(fn()).toEqual(7)
})
```
</details>
### and
```typescript
and<T extends { and?: ((...a: readonly any[]) => any)
```
Returns `true` if both arguments are `true`. Otherwise, it returns `false`.
```javascript
R.and(true, true); // => true
R.and(false, true); // => false
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.and(true%2C%20true)%3B%20%2F%2F%20%3D%3E%20true%0AR.and(false%2C%20true)%3B%20%2F%2F%20%3D%3E%20false">Try the above <strong>R.and</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
and<T extends { and?: ((...a: readonly any[]) => any); } | number | boolean | string | null>(fn1: T, val2: any): boolean;
and<T extends { and?: ((...a: readonly any[]) => any); } | number | boolean | string | null>(fn1: T): (val2: any) => boolean;
```
</details>
<details>
<summary><strong>R.and</strong> source</summary>
```javascript
export function and(a, b){
if (arguments.length === 1) return _b => and(a, _b)
return a && b
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { and } from './and'
test('happy', () => {
expect(and(true, true)).toBe(true)
expect(and(true, false)).toBe(false)
expect(and(false, true)).toBe(false)
expect(and(false, false)).toBe(false)
})
```
</details>
### any
```typescript
any<T>(predicate: (x: T, i: number) => boolean, list: ReadonlyArray<T>): boolean
```
It returns `true`, if at least one member of `list` returns true, when passed to `predicate` function.
```javascript
const list = [1, 2, 3]
const predicate = x => x * x > 8
R.any(fn, list)
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20const%20list%20%3D%20%5B1%2C%202%2C%203%5D%0Aconst%20predicate%20%3D%20x%20%3D%3E%20x%20*%20x%20%3E%208%0AR.any(fn%2C%20list)%0A%2F%2F%20%3D%3E%20true">Try the above <strong>R.any</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
any<T>(predicate: (x: T, i: number) => boolean, list: ReadonlyArray<T>): boolean;
any<T>(predicate: (x: T) => boolean, list: ReadonlyArray<T>): boolean;
any<T>(predicate: (x: T, i: number) => boolean): (list: ReadonlyArray<T>) => boolean;
any<T>(predicate: (x: T) => boolean): (list: ReadonlyArray<T>) => boolean;
```
</details>
<details>
<summary><strong>R.any</strong> 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
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { any } from './any'
const arr = [ 1, 2 ]
test('no curry', () => {
expect(any(val => val < 0, arr)).toBeFalse()
})
test('with curry', () => {
expect(any(val => val < 2)(arr)).toBeTrue()
})
test('passes index to predicate', () => {
any((x, i) => {
expect(typeof x).toBe('string')
expect(typeof i).toBe('number')
})([ 'foo', 'bar' ])
})
```
</details>
<details>
<summary><strong>Typescript</strong> test</summary>
```typescript
import {any} from 'rambda'
describe('any', () => {
it('1', () => {
const x = any<number>(
(y, i) => {
y // $ExpectType number
i // $ExpectType number
return y > 2
},
[1, 2, 3]
)
x // $ExpectType boolean
})
it('2', () => {
const x = any<number>(
y => {
y // $ExpectType number
return y > 2
},
[1, 2, 3]
)
x // $ExpectType boolean
})
it('1 curry', () => {
const x = any<number>((y, i) => {
y // $ExpectType number
i // $ExpectType number
return y > 2
})([1, 2, 3])
x // $ExpectType boolean
})
it('2 curry', () => {
const x = any<number>(y => {
y // $ExpectType number
return y > 2
})([1, 2, 3])
x // $ExpectType boolean
})
})
```
</details>
### anyPass
```typescript
anyPass<T>(predicates: ReadonlyArray<SafePred<T>>): SafePred<T>
```
It accepts list of `predicates` and returns a function. This function with its `input` will return `true`, if any of `predicates` returns `true` for this `input`.
```javascript
const isBig = x => x > 20
const isOdd = x => x % 2 === 1
const input = 11
const fn = R.anyPass(
[isBig, isOdd]
)
const result = fn(input)
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20isBig%20%3D%20x%20%3D%3E%20x%20%3E%2020%0Aconst%20isOdd%20%3D%20x%20%3D%3E%20x%20%25%202%20%3D%3D%3D%201%0Aconst%20input%20%3D%2011%0A%0Aconst%20fn%20%3D%20R.anyPass(%0A%20%20%5BisBig%2C%20isOdd%5D%0A)%0A%0Aconst%20result%20%3D%20fn(input)%20%0A%2F%2F%20%3D%3E%20true">Try the above <strong>R.anyPass</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
anyPass<T>(predicates: ReadonlyArray<SafePred<T>>): SafePred<T>;
```
</details>
<details>
<summary><strong>R.anyPass</strong> 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
}
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { anyPass } from './anyPass'
test('happy', () => {
const rules = [ x => typeof x === 'string', x => x > 10 ]
const predicate = anyPass(rules)
expect(predicate('foo')).toBeTrue()
expect(predicate(6)).toBeFalse()
})
test('happy', () => {
const rules = [ x => typeof x === 'string', x => x > 10 ]
expect(anyPass(rules)(11)).toBeTrue()
expect(anyPass(rules)(undefined)).toBeFalse()
})
const obj = {
a : 1,
b : 2,
}
test('when returns true', () => {
const conditionArr = [ val => val.a === 1, val => val.a === 2 ]
expect(anyPass(conditionArr)(obj)).toBeTrue()
})
test('when returns false + curry', () => {
const conditionArr = [ val => val.a === 2, val => val.b === 3 ]
expect(anyPass(conditionArr)(obj)).toBeFalse()
})
test('happy', () => {
expect(anyPass([])(3)).toEqual(false)
})
```
</details>
<details>
<summary><strong>Typescript</strong> test</summary>
```typescript
import {anyPass} 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
})
})
```
</details>
<details>
<summary>1 failed <italic>Ramda.anyPass</italic> specs
> Reason for the failure: ramda returns a curried function whose arity matches that of the highest-arity predicate
</summary>
```javascript
var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
describe('anyPass', function() {
var odd = function(n) { return n % 2 !== 0; };
var gt20 = function(n) { return n > 20; };
var lt5 = function(n) { return n < 5; };
var plusEq = function(w, x, y, z) { return w + x === y + z; };
it('returns a curried function whose arity matches that of the highest-arity predicate', function() {
eq(R.anyPass([odd, lt5, plusEq]).length, 4);
eq(R.anyPass([odd, lt5, plusEq])(6, 7, 8, 9), false);
eq(R.anyPass([odd, lt5, plusEq])(6)(7)(8)(9), false);
});
});
```
</details>
### append
```typescript
append<T>(x: T, listOrString: ReadonlyArray<T>): T[]
```
It adds element `x` at the end of `listOrString`.
```javascript
const x = 'foo'
const result = [
R.append(x, 'cherry_'),
R.append(x, ['bar', 'baz'])
]
// => ['cherry_foo', ['bar', 'baz', 'foo']]
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20x%20%3D%20'foo'%0A%0Aconst%20result%20%3D%20%5B%0A%20%20R.append(x%2C%20'cherry_')%2C%0A%20%20R.append(x%2C%20%5B'bar'%2C%20'baz'%5D)%0A%5D%0A%2F%2F%20%3D%3E%20%5B'cherry_foo'%2C%20%5B'bar'%2C%20'baz'%2C%20'foo'%5D%5D">Try the above <strong>R.append</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
append<T>(x: T, listOrString: ReadonlyArray<T>): T[];
append<T>(x: T): <T>(listOrString: ReadonlyArray<T>) => T[];
```
</details>
<details>
<summary><strong>R.append</strong> source</summary>
```javascript
export function append(x, listOrString){
if (arguments.length === 1)
return _listOrString => append(x, _listOrString)
if (typeof listOrString === 'string') return `${ listOrString }${ x }`
const clone = listOrString.slice()
clone.push(x)
return clone
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { append } from './append'
import { compose } from './compose.js'
import { flatten } from './flatten.js'
import { map } from './map'
test('with strings', () => {
expect(append('o', 'fo')).toEqual('foo')
})
test('with arrays', () => {
expect(append('tests', [ 'write', 'more' ])).toEqual([
'write',
'more',
'tests',
])
})
test('append to empty array', () => {
expect(append('tests', [])).toEqual([ 'tests' ])
})
test('happy', () => {
const result = compose(flatten, map(append(0)))([ [ 1 ], [ 2 ], [ 3 ] ])
expect(result).toEqual([ 1, 0, 2, 0, 3, 0 ])
})
test('should not modify arguments', () => {
const a = [ 1, 2, 3 ]
const b = append(4, a)
expect(a).toEqual([ 1, 2, 3 ])
expect(b).toEqual([ 1, 2, 3, 4 ])
})
```
</details>
### applySpec
```typescript
applySpec<Spec extends Record<string, (...args: readonly any[]) => any>>(
spec: Spec
): (
...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
```
It returns a curried function with the same arity as the longest function in the spec object.
Arguments will be applied to the spec methods recursively.
```javascript
const spec = {
name: R.path('deeply.nested.firstname')
}
const json = {
deeply: {
nested: {
firstname: 'barry'
}
}
}
const result = R.applySpec(spec, json) // => { name: 'barry' }
// Second example
const getMetrics = R.applySpec({
sum: R.add,
nested: { mul: R.multiply }
});
getMetrics(2, 4); // => { sum: 6, nested: { mul: 8 } }
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20spec%20%3D%20%7B%0A%20%20name%3A%20R.path('deeply.nested.firstname')%0A%7D%0Aconst%20json%20%3D%20%7B%0A%20%20deeply%3A%20%7B%0A%20%20%20nested%3A%20%7B%0A%20%20%20%20%20%20firstname%3A%20'barry'%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%0Aconst%20result%20%3D%20R.applySpec(spec%2C%20json)%20%2F%2F%20%3D%3E%20%7B%20name%3A%20'barry'%20%7D%0A%0A%2F%2F%20Second%20example%0Aconst%20getMetrics%20%3D%20R.applySpec(%7B%0A%20%20sum%3A%20R.add%2C%0A%20%20nested%3A%20%7B%20mul%3A%20R.multiply%20%7D%0A%7D)%3B%0AgetMetrics(2%2C%204)%3B%20%2F%2F%20%3D%3E%20%7B%20sum%3A%206%2C%20nested%3A%20%7B%20mul%3A%208%20%7D%20%7D">Try the above <strong>R.applySpec</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
applySpec<Spec extends Record<string, (...args: readonly any[]) => any>>(
spec: Spec
): (
...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> };
applySpec<T>(spec: any): (...args: readonly any[]) => T;
```
</details>
<details>
<summary><strong>R.applySpec</strong> source</summary>
```javascript
// recursively traverse the given spec object to find the highest arity function
function __findHighestArity(spec, max = 0){
for (const key in spec){
if (spec.hasOwnProperty(key) === false || key === 'constructor') continue
if (typeof spec[ key ] === 'object'){
max = Math.max(max, __findHighestArity(spec[ key ]))
}
if (typeof spec[ key ] === 'function'){
max = Math.max(max, spec[ key ].length)
}
}
return max
}
function __filterUndefined(){
const defined = []
let i = 0
const l = arguments.length
while (i < l){
if (typeof arguments[ i ] === 'undefined') break
defined[ i ] = arguments[ i ]
i++
}
return defined
}
function __applySpecWithArity(
spec, arity, cache
){
const remaining = arity - cache.length
if (remaining === 1)
return x =>
__applySpecWithArity(
spec, arity, __filterUndefined(...cache, x)
)
if (remaining === 2)
return (x, y) =>
__applySpecWithArity(
spec, arity, __filterUndefined(
...cache, x, y
)
)
if (remaining === 3)
return (
x, y, z
) =>
__applySpecWithArity(
spec, arity, __filterUndefined(
...cache, x, y, z
)
)
if (remaining === 4)
return (
x, y, z, a
) =>
__applySpecWithArity(
spec,
arity,
__filterUndefined(
...cache, x, y, z, a
)
)
if (remaining > 4)
return (...args) =>
__applySpecWithArity(
spec, arity, __filterUndefined(...cache, ...args)
)
// handle spec as Array
if (Array.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' || Array.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
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { applySpec as applySpecRamda, nAry } from 'ramda'
import { add, always, compose, dec, inc, map, path, prop, T } from '../rambda'
import { applySpec } from './applySpec'
test('different than Ramda when bad spec', () => {
const result = applySpec({ sum : { a : 1 } })(1, 2)
const ramdaResult = applySpecRamda({ sum : { a : 1 } })(1, 2)
expect(result).toEqual({})
expect(ramdaResult).toEqual({ sum : { a : {} } })
})
test('works with empty spec', () => {
expect(applySpec({})()).toEqual({})
expect(applySpec([])(1, 2)).toEqual({})
expect(applySpec(null)(1, 2)).toEqual({})
})
test('works with unary functions', () => {
const result = applySpec({
v : inc,
u : dec,
})(1)
const expected = {
v : 2,
u : 0,
}
expect(result).toEqual(expected)
})
test('works with binary functions', () => {
const result = applySpec({ sum : add })(1, 2)
expect(result).toEqual({ sum : 3 })
})
test('works with nested specs', () => {
const result = applySpec({
unnested : always(0),
nested : { sum : add },
})(1, 2)
const expected = {
unnested : 0,
nested : { sum : 3 },
}
expect(result).toEqual(expected)
})
test('works with arrays of nested specs', () => {
const result = applySpec({
unnested : always(0),
nested : [ { sum : add } ],
})(1, 2)
expect(result).toEqual({
unnested : 0,
nested : [ { sum : 3 } ],
})
})
test('works with arrays of spec objects', () => {
const result = applySpec([ { sum : add } ])(1, 2)
expect(result).toEqual([ { sum : 3 } ])
})
test('works with arrays of functions', () => {
const result = applySpec([ map(prop('a')), map(prop('b')) ])([
{
a : 'a1',
b : 'b1',
},
{
a : 'a2',
b : 'b2',
},
])
const expected = [
[ 'a1', 'a2' ],
[ 'b1', 'b2' ],
]
expect(result).toEqual(expected)
})
test('works with a spec defining a map key', () => {
expect(applySpec({ map : prop('a') })({ a : 1 })).toEqual({ map : 1 })
})
test.skip('retains the highest arity', () => {
const f = applySpec({
f1 : nAry(2, T),
f2 : nAry(5, T),
})
expect(f.length).toBe(5)
})
test('returns a curried function', () => {
expect(applySpec({ sum : add })(1)(2)).toEqual({ sum : 3 })
})
// Additional tests
// ============================================
test('arity', () => {
const spec = {
one : x1 => x1,
two : (x1, x2) => x1 + x2,
three : (
x1, x2, x3
) => x1 + x2 + x3,
}
expect(applySpec(
spec, 1, 2, 3
)).toEqual({
one : 1,
two : 3,
three : 6,
})
})
test('arity over 5 arguments', () => {
const spec = {
one : x1 => x1,
two : (x1, x2) => x1 + x2,
three : (
x1, x2, x3
) => x1 + x2 + x3,
four : (
x1, x2, x3, x4
) => x1 + x2 + x3 + x4,
five : (
x1, x2, x3, x4, x5
) => x1 + x2 + x3 + x4 + x5,
}
expect(applySpec(
spec, 1, 2, 3, 4, 5
)).toEqual({
one : 1,
two : 3,
three : 6,
four : 10,
five : 15,
})
})
test('curried', () => {
const spec = {
one : x1 => x1,
two : (x1, x2) => x1 + x2,
three : (
x1, x2, x3
) => x1 + x2 + x3,
}
expect(applySpec(spec)(1)(2)(3)).toEqual({
one : 1,
two : 3,
three : 6,
})
})
test('curried over 5 arguments', () => {
const spec = {
one : x1 => x1,
two : (x1, x2) => x1 + x2,
three : (
x1, x2, x3
) => x1 + x2 + x3,
four : (
x1, x2, x3, x4
) => x1 + x2 + x3 + x4,
five : (
x1, x2, x3, x4, x5
) => x1 + x2 + x3 + x4 + x5,
}
expect(applySpec(spec)(1)(2)(3)(4)(5)).toEqual({
one : 1,
two : 3,
three : 6,
four : 10,
five : 15,
})
})
test('undefined property', () => {
const spec = { prop : path([ 'property', 'doesnt', 'exist' ]) }
expect(applySpec(spec, {})).toEqual({ prop : undefined })
})
test('restructure json object', () => {
const spec = {
id : path('user.id'),
name : path('user.firstname'),
profile : path('user.profile'),
doesntExist : path('user.profile.doesntExist'),
info : { views : compose(inc, prop('views')) },
type : always('playa'),
}
const data = {
user : {
id : 1337,
firstname : 'john',
lastname : 'shaft',
profile : 'shaft69',
},
views : 42,
}
expect(applySpec(spec, data)).toEqual({
id : 1337,
name : 'john',
profile : 'shaft69',
doesntExist : undefined,
info : { views : 43 },
type : 'playa',
})
})
```
</details>
<details>
<summary><strong>Typescript</strong> test</summary>
```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
})
})
```
</details>
### assoc
```typescript
assoc<T, U, K extends string>(prop: K, newValue: T, obj: U): Record<K, T> & U
```
It makes a shallow clone of `obj` with setting or overriding the property `prop` with `newValue`.
```javascript
R.assoc('c', 3, {a: 1, b: 2})
//=> {a: 1, b: 2, c: 3}
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.assoc('c'%2C%203%2C%20%7Ba%3A%201%2C%20b%3A%202%7D)%0A%2F%2F%3D%3E%20%7Ba%3A%201%2C%20b%3A%202%2C%20c%3A%203%7D">Try the above <strong>R.assoc</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
assoc<T, U, K extends string>(prop: K, newValue: T, obj: U): Record<K, T> & U;
assoc<T, K extends string>(prop: K, newValue: T): <U>(obj: U) => Record<K, T> & U;
assoc<K extends string>(prop: K): <T, U>(newValue: T, obj: U) => Record<K, T> & U;
```
</details>
<details>
<summary><strong>R.assoc</strong> source</summary>
```javascript
import { curry } from './curry'
function assocFn(
prop, newValue, obj
){
return Object.assign(
{}, obj, { [ prop ] : newValue }
)
}
export const assoc = curry(assocFn)
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { assoc } from './assoc'
test('adds a key to an empty object', () => {
expect(assoc(
'a', 1, {}
)).toEqual({ a : 1 })
})
test('adds a key to a non-empty object', () => {
expect(assoc(
'b', 2, { a : 1 }
)).toEqual({
a : 1,
b : 2,
})
})
test('adds a key to a non-empty object - curry case 1', () => {
expect(assoc('b', 2)({ a : 1 })).toEqual({
a : 1,
b : 2,
})
})
test('adds a key to a non-empty object - curry case 2', () => {
expect(assoc('b')(2, { a : 1 })).toEqual({
a : 1,
b : 2,
})
})
test('adds a key to a non-empty object - curry case 3', () => {
const result = assoc('b')(2)({ a : 1 })
expect(result).toEqual({
a : 1,
b : 2,
})
})
test('changes an existing key', () => {
expect(assoc(
'a', 2, { a : 1 }
)).toEqual({ a : 2 })
})
test('undefined is considered an empty object', () => {
expect(assoc(
'a', 1, undefined
)).toEqual({ a : 1 })
})
test('null is considered an empty object', () => {
expect(assoc(
'a', 1, null
)).toEqual({ a : 1 })
})
test('value can be null', () => {
expect(assoc(
'a', null, null
)).toEqual({ a : null })
})
test('value can be undefined', () => {
expect(assoc(
'a', undefined, null
)).toEqual({ a : undefined })
})
test('assignment is shallow', () => {
expect(assoc(
'a', { b : 2 }, { a : { c : 3 } }
)).toEqual({ a : { b : 2 } })
})
```
</details>
### assocPath
```typescript
assocPath<T, U>(path: Path, newValue: T, obj: U): U
```
It makes a shallow clone of `obj` with setting or overriding with `newValue` the property found with `path`.
```javascript
const path = 'b.c'
const newValue = 2
const obj = { a: 1 }
R.assocPath(path, newValue, obj)
// => { a : 1, b : { c : 2 }}
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20const%20path%20%3D%20'b.c'%0Aconst%20newValue%20%3D%202%0Aconst%20obj%20%3D%20%7B%20a%3A%201%20%7D%0A%0AR.assocPath(path%2C%20newValue%2C%20obj)%0A%2F%2F%20%3D%3E%20%7B%20a%20%3A%201%2C%20b%20%3A%20%7B%20c%20%3A%202%20%7D%7D">Try the above <strong>R.assocPath</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
assocPath<T, U>(path: Path, newValue: T, obj: U): U;
assocPath<T, U>(path: Path, newValue: T): (obj: U) => U;
assocPath<T, U>(path: Path): FToolbelt.Curry<(a: T, b: U) => U>;
```
</details>
<details>
<summary><strong>R.assocPath</strong> source</summary>
```javascript
import { _isInteger } from './_internals/_isInteger'
import { assoc } from './assoc'
import { curry } from './curry'
function assocPathFn(
list, newValue, input
){
const pathArrValue = typeof list === 'string' ? list.split('.') : list
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(parseInt(pathArrValue[ 1 ], 10)) ?
[] :
{} :
input[ index ]
newValue = assocPathFn(
Array.prototype.slice.call(pathArrValue, 1),
newValue,
nextinput
)
}
if (_isInteger(parseInt(index, 10)) && Array.isArray(input)){
const arr = input.slice()
arr[ index ] = newValue
return arr
}
return assoc(
index, newValue, input
)
}
export const assocPath = curry(assocPathFn)
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { assocPath } from './assocPath'
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 nested array to a non-empty object - curry case 1', () => {
expect(assocPath('b.0', 2)({ a : 1 })).toEqual({
a : 1,
b : [ 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('happy', () => {
const result = assocPath(
[], 3, {
a : 1,
b : 2,
}
)
expect(result).toEqual(3)
})
test('happy', () => {
const expected = { foo : { bar : { baz : 42 } } }
const result = assocPath(
[ 'foo', 'bar', 'baz' ], 42, { foo : null }
)
expect(result).toEqual(expected)
})
```
</details>
### both
```typescript
both(pred1: Pred, pred2: Pred): Pred
```
It returns a function with `input` argument.
This function will return `true`, if both `firstCondition` and `secondCondition` return `true` when `input` is passed as their argument.
```javascript
const firstCondition = x => x > 10
const secondCondition = x => x < 20
const fn = R.both(secondCondition)
const result = [fn(15), fn(30)]
// => [true, false]
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20firstCondition%20%3D%20x%20%3D%3E%20x%20%3E%2010%0Aconst%20secondCondition%20%3D%20x%20%3D%3E%20x%20%3C%2020%0Aconst%20fn%20%3D%20R.both(secondCondition)%0A%0Aconst%20result%20%3D%20%5Bfn(15)%2C%20fn(30)%5D%0A%2F%2F%20%3D%3E%20%5Btrue%2C%20false%5D">Try the above <strong>R.both</strong> example in Rambda REPL</a>
<details>
<summary>All Typescript definitions</summary>
```typescript
both(pred1: Pred, pred2: Pred): Pred;
both<T>(pred1: Predicate<T>, pred2: Predicate<T>): Predicate<T>;
both<T>(pred1: Predicate<T>): (pred2: Predicate<T>) => Predicate<T>;
both(pred1: Pred): (pred2: Pred) => Pred;
```
</details>
<details>
<summary><strong>R.both</strong> source</summary>
```javascript
export function both(f, g){
if (arguments.length === 1) return _g => both(f, _g)
return (...input) => f(...input) && g(...input)
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { both } from './both'
const firstFn = val => val > 0
const secondFn = val => val < 10
test('with curry', () => {
expect(both(firstFn)(secondFn)(17)).toBeFalse()
})
test('without curry', () => {
expect(both(firstFn, secondFn)(7)).toBeTrue()
})
test('with multiple inputs', () => {
const between = function (
a, b, c
){
return a < b && b < c
}
const total20 = function (
a, b, c
){
return a + b + c === 20
}
const fn = both(between, total20)
expect(fn(
5, 7, 8
)).toBeTrue()
})
test('skip evaluation of the second expression', () => {
let effect = 'not evaluated'
const F = function (){
return false
}
const Z = function (){
effect = 'Z got evaluated'
}
both(F, Z)()
expect(effect).toBe('not evaluated')
})
```
</details>
<details>
<summary><strong>Typescript</strong> test</summary>
```typescript
import {both} from 'rambda'
describe('both', () => {
it('with passed type', () => {
const fn = both<number>( // $ExpectType Predicate<number>
x => {
return x > 1
},
x => {
return x % 2 === 0
}
)
const result = fn(2) // $ExpectType boolean
result // $ExpectType boolean
})
it('no type passed', () => {
const fn = both(
x => {
x // $ExpectType any
return x > 1
},
x => {
return x % 2 === 0
}
)
const result = fn(2) // $ExpectType boolean
result // $ExpectType boolean
})
})
describe('both + curry', () => {
it('with passed type', () => {
const fn = both<number>(x => {
return x > 1
})(x => {
return x % 2 === 0
})
fn // $ExpectType Predicate<number>
const result = fn(2) // $ExpectType boolean
result // $ExpectType boolean
})
it('no type passed', () => {
const fn = both(x => {
x // $ExpectType unknown
return (x as number) > 1
})(x => {
return (x as number) % 2 === 0
})
const result = fn(2) // $ExpectType boolean
result // $ExpectType boolean
})
})
```
</details>
<details>
<summary>1 failed <italic>Ramda.both</italic> specs
> Reason for the failure: ramda supports fantasy-land
</summary>
```javascript
var S = require('sanctuary');
var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
describe('both', function() {
it('accepts fantasy-land applicative functors', function() {
var Just = S.Jus