rambdax
Version:
Extended version of Rambda - a lightweight, faster alternative to Ramda
1,946 lines (1,425 loc) • 498 kB
Markdown
# Rambdax
Extended version of Rambda(utility library) - [Documentation](https://selfrefactor.github.io/rambdax/#/)
`Rambda` is smaller and faster alternative to the popular functional programming library **Ramda**. - [Documentation](https://selfrefactor.github.io/rambda/#/)
[](https://packagephobia.com/result?p=rambdax)
[](https://github.com/selfrefactor/rambdax/graphs/contributors)
[](https://codecov.io/gh/selfrefactor/rambda)

## ❯ Differences between Rambda and Rambdax
Rambdax passthrough all [Rambda](https://github.com/selfrefactor/rambda) methods and introduce some new functions.
The idea of **Rambdax** is to extend **Rambda** without worring for **Ramda** compatibility.
[](#-differences-between-rambda-and-rambdax)
## ❯ Example use
```javascript
import { composeAsync, filter, delay, mapAsync } from 'rambdax'
const result = await composeAsync(
mapAsync(async x => {
await delay(100)
return x + 1
}),
filter(x => x > 1)
)([1, 2, 3])
// => [3, 4]
```
You can test this example in <a href="https://rambda.now.sh?const%20result%20%3D%20await%20R.composeAsync(%0A%20%20R.mapAsync(async%20x%20%3D%3E%20%7B%0A%20%20%20%20await%20R.delay(100)%0A%20%20%20%20return%20x%20%2B%201%0A%20%20%7D)%2C%0A%20%20R.filter(x%20%3D%3E%20x%20%3E%201)%0A)(%5B1%2C%202%2C%203%5D)%0A%2F%2F%20%3D%3E%20%5B3%2C%204%5D">Rambda's REPL</a>
* [Differences between Rambda and Ramda](#differences-between-rambda-and-ramda)
* [API](#api)
* [Changelog](#-changelog)
[](#-example-use)
## ❯ Rambdax's advantages
### TypeScript included
TypeScript definitions are included in the library, in comparison to **Ramda**, where you need to additionally install `@types/ramda`.
Still, you need to be aware that functional programming features in `TypeScript` are in development, which means that using **R.compose/R.pipe** can be problematic.
Important - Rambdax version `9.0.0`(or higher) requires TypeScript version `4.3.3`(or higher).
### Dot notation for `R.path`, `R.paths`, `R.assocPath` and `R.lensPath`
Standard usage of `R.path` is `R.path(['a', 'b'], {a: {b: 1} })`.
In **Rambda** you have the choice to use dot notation(which is arguably more readable):
```
R.path('a.b', {a: {b: 1} })
```
### Comma notation for `R.pick` and `R.omit`
Similar to dot notation, but the separator is comma(`,`) instead of dot(`.`).
```
R.pick('a,b', {a: 1 , b: 2, c: 3} })
// No space allowed between properties
```
### Extendable with Ramda community projects
`Rambdax` implements some methods from `Ramda` community projects, such as `R.lensSatisfies`, `R.lensEq` and `R.viewOr`.
### Understandable source code due to little usage of internals
`Ramda` uses a lot of internals, which hides a lot of logic. Reading the full source code of a method can be challenging.
### Better VSCode experience
If the project is written in Javascript, then `go to source definition` action will lead you to actual implementation of the method.
### Deno support
```
import * as R from "https://deno.land/x/rambdax/mod.ts";
```
### Alternative TS definitions
Alternative TS definitions are available as `rambdax/immutable`. These are Rambdax definitions linted with ESLint `functional/prefer-readonly-type` plugin.
[](#-rambdaxs-advantages)
## ❯ Missing Ramda methods
<details>
<summary>
Click to see the full list of 46 Ramda methods not implemented in Rambda and their status.
</summary>
- construct - Using classes is not very functional programming oriented.
- constructN - same as above
- into - no support for transducer as it is overly complex to implement, understand and read.
- invert - overly complicated and limited use case
- invertObj
- invoker
- keysIn - we shouldn't encourage extending object with `.prototype`
- lift
- liftN
- mapAccum - `Ramda` example doesn't looks convincing
- mapAccumRight
- memoizeWith - hard to imagine its usage in context of `R.pipe`/`R.compose`
- mergeDeepWith - limited use case
- mergeDeepWithKey
- mergeWithKey
- nAry - hard to argument about and hard to create meaningful TypeScript definitions
- nthArg - limited use case
- o - enough TypeScript issues with `R.pipe`/`R.compose` to add more composition methods
- otherwise - naming is confusing
- pair - `left-pad` types of debacles happens partially because of such methods that should not be hidden, bur rather part of your code base even if they need to exist.
- partialRight - I dislike `R.partial`, so I don't want to add more methods that are based on it
- pipeWith
- project - naming is confusing, but also limited use case
- promap
- reduceRight - I find `right/left` methods confusing so I added them only where it makes sense.
- reduceWhile - functions with 4 inputs - I think that even 3 is too much
- reduced
- remove - nice name but it is too generic. Also, `Rambdax` has such method and there it works very differently
- scan - hard to explain
- sequence
- splitWhenever
- symmetricDifferenceWith
- andThen
- toPairsIn
- transduce - currently is out of focus
- traverse - same as above
- unary
- uncurryN
- unfold - similar to `R.scan` and I find that it doesn't help with readability
- unionWith - why it has its usage, I want to limit number of methods that accept more than 2 arguments
- until
- useWith - hard to explain
- valuesIn
- xprod - limited use case
- thunkify
- __ - placeholder method allows user to further customize the method call. While, it seems useful initially, the price is too high in terms of complexity for TypeScript definitions. If it is not easy exressable in TypeScript, it is not worth it as **Rambda** is a TypeScript first library.
The following methods are not going to be added(reason for exclusion is provided as a comment):
</details>
[](#-missing-ramda-methods)
## ❯ Install
- **yarn add rambdax**
- For UMD usage either use `./dist/rambdax.umd.js` or the following CDN link:
```
https://unpkg.com/rambdax@CURRENT_VERSION/dist/rambdax.umd.js
```
- with deno
```
import {add} from "https://deno.land/x/rambda/mod.ts";
```
[](#-install)
## 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.
[](#-differences-between-rambda-and-ramda)
## ❯ 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.21) and *Ramda*(0.30.1).
</summary>
method | Rambda | Ramda | Lodash
--- |--- | --- | ---
*add* | 🚀 Fastest | 21.52% slower | 82.15% slower
*adjust* | 8.48% slower | 🚀 Fastest | 🔳
*all* | 🚀 Fastest | 7.18% slower | 🔳
*allPass* | 🚀 Fastest | 88.25% slower | 🔳
*allPass* | 🚀 Fastest | 98.56% slower | 🔳
*and* | 🚀 Fastest | 89.09% slower | 🔳
*any* | 🚀 Fastest | 92.87% slower | 45.82% slower
*anyPass* | 🚀 Fastest | 98.25% slower | 🔳
*append* | 🚀 Fastest | 2.07% slower | 🔳
*applySpec* | 🚀 Fastest | 80.43% slower | 🔳
*assoc* | 72.32% slower | 60.08% slower | 🚀 Fastest
*clone* | 🚀 Fastest | 91.86% slower | 86.48% slower
*compose* | 6.07% slower | 16.89% slower | 🚀 Fastest
*converge* | 78.63% slower | 🚀 Fastest | 🔳
*curry* | 🚀 Fastest | 28.86% slower | 🔳
*curryN* | 🚀 Fastest | 41.05% slower | 🔳
*defaultTo* | 🚀 Fastest | 48.91% slower | 🔳
*drop* | 🚀 Fastest | 82.35% slower | 🔳
*dropLast* | 🚀 Fastest | 86.74% slower | 🔳
*equals* | 58.37% slower | 96.73% slower | 🚀 Fastest
*filter* | 6.7% slower | 72.03% slower | 🚀 Fastest
*find* | 🚀 Fastest | 85.14% slower | 42.65% slower
*findIndex* | 🚀 Fastest | 86.48% slower | 72.27% slower
*flatten* | 🚀 Fastest | 85.68% slower | 3.57% slower
*ifElse* | 🚀 Fastest | 58.56% slower | 🔳
*includes* | 🚀 Fastest | 81.64% slower | 🔳
*indexOf* | 🚀 Fastest | 80.17% slower | 🔳
*indexOf* | 🚀 Fastest | 82.2% slower | 🔳
*init* | 🚀 Fastest | 92.24% slower | 13.3% slower
*is* | 🚀 Fastest | 57.69% slower | 🔳
*isEmpty* | 🚀 Fastest | 97.14% slower | 54.99% slower
*last* | 🚀 Fastest | 93.43% slower | 5.28% slower
*lastIndexOf* | 🚀 Fastest | 85.19% slower | 🔳
*map* | 🚀 Fastest | 86.6% slower | 11.73% slower
*match* | 🚀 Fastest | 44.83% slower | 🔳
*merge* | 🚀 Fastest | 12.21% slower | 55.76% slower
*none* | 🚀 Fastest | 96.48% slower | 🔳
*objOf* | 🚀 Fastest | 38.05% slower | 🔳
*omit* | 🚀 Fastest | 69.95% slower | 97.34% slower
*over* | 🚀 Fastest | 56.23% slower | 🔳
*path* | 37.81% slower | 77.81% slower | 🚀 Fastest
*pick* | 🚀 Fastest | 19.07% slower | 80.2% slower
*pipe* | 🚀 Fastest | 0.11% slower | 🔳
*prop* | 🚀 Fastest | 87.95% slower | 🔳
*propEq* | 🚀 Fastest | 91.92% slower | 🔳
*range* | 🚀 Fastest | 61.8% slower | 57.44% slower
*reduce* | 60.48% slower | 77.1% slower | 🚀 Fastest
*repeat* | 48.57% slower | 68.98% slower | 🚀 Fastest
*replace* | 33.45% slower | 33.99% slower | 🚀 Fastest
*set* | 🚀 Fastest | 50.35% slower | 🔳
*sort* | 🚀 Fastest | 40.23% slower | 🔳
*sortBy* | 🚀 Fastest | 25.29% slower | 56.88% slower
*split* | 🚀 Fastest | 55.37% slower | 17.64% slower
*splitEvery* | 🚀 Fastest | 71.98% slower | 🔳
*take* | 🚀 Fastest | 91.96% slower | 4.72% slower
*takeLast* | 🚀 Fastest | 93.39% slower | 19.22% slower
*test* | 🚀 Fastest | 82.34% slower | 🔳
*type* | 🚀 Fastest | 48.6% slower | 🔳
*uniq* | 🚀 Fastest | 84.9% slower | 🔳
*uniqBy* | 51.93% slower | 🚀 Fastest | 🔳
*uniqWith* | 8.29% slower | 🚀 Fastest | 🔳
*uniqWith* | 14.23% slower | 🚀 Fastest | 🔳
*update* | 🚀 Fastest | 52.35% slower | 🔳
*view* | 🚀 Fastest | 76.15% slower | 🔳
</details>
[](#-benchmarks)
## ❯ Used by
- [ESLint plugin Mocha](https://www.npmjs.com/package/eslint-plugin-mocha)
- [WatermelonDB](https://github.com/Nozbe/WatermelonDB)
- [Walmart Canada](https://www.walmart.ca) reported by [w-b-dev](https://github.com/w-b-dev)
- [VSCode Slack integration](https://github.com/verydanny/vcslack)
- [Webpack PostCSS](https://github.com/sectsect/webpack-postcss)
- [MobX-State-Tree decorators](https://github.com/farwayer/mst-decorators)
- [Rewrite of the Betaflight configurator](https://github.com/freshollie/fresh-configurator)
- [MineFlayer plugin](https://github.com/G07cha/MineflayerArmorManager)
[](#-used-by)
## API
### add
It adds `a` and `b`.
> :boom: It doesn't work with strings, as the inputs are parsed to numbers before calculation.
<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 this <strong>R.add</strong> example in Rambda REPL</a>
[](#add)
### addIndex
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.addIndex(R.map)((val%2C%20idx)%20%3D%3E%20val%20%2B%20idx%20%2B%201%2C%20%5B1%2C%202%2C%203%5D)%0A%2F%2F%20%3D%3E%20%5B2%2C%204%2C%206%5D">Try this <strong>R.addIndex</strong> example in Rambda REPL</a>
[](#addIndex)
### addIndexRight
Same as `R.addIndex`, but it will passed indexes are decreasing, instead of increasing.
[](#addIndexRight)
### 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])`.
```javascript
const result = 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 this <strong>R.adjust</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.adjust</strong> source</summary>
```javascript
import { cloneList } from './_internals/cloneList.js'
import { curry } from './curry.js'
function adjustFn(
index, replaceFn, list
){
const actualIndex = index < 0 ? list.length + index : index
if (index >= list.length || actualIndex < 0) return list
const clone = cloneList(list)
clone[ actualIndex ] = replaceFn(clone[ actualIndex ])
return clone
}
export const adjust = curry(adjustFn)
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { add } from './add.js'
import { adjust } from './adjust.js'
import { pipe } from './pipe.js'
const list = [ 0, 1, 2 ]
const expected = [ 0, 11, 2 ]
test('happy', () => {})
test('happy', () => {
expect(adjust(
1, add(10), list
)).toEqual(expected)
})
test('with curring type 1 1 1', () => {
expect(adjust(1)(add(10))(list)).toEqual(expected)
})
test('with curring type 1 2', () => {
expect(adjust(1)(add(10), list)).toEqual(expected)
})
test('with curring type 2 1', () => {
expect(adjust(1, add(10))(list)).toEqual(expected)
})
test('with negative index', () => {
expect(adjust(
-2, add(10), list
)).toEqual(expected)
})
test('when index is out of bounds', () => {
const list = [ 0, 1, 2, 3 ]
expect(adjust(
4, add(1), list
)).toEqual(list)
expect(adjust(
-5, add(1), list
)).toEqual(list)
})
```
</details>
[](#adjust)
### 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.
```javascript
const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > -1
const result = R.all(predicate, list)
// => 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%20list)%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.all</strong> example in Rambda REPL</a>
<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 ])) return false
}
return true
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { all } from './all.js'
const list = [ 0, 1, 2, 3, 4 ]
test('when true', () => {
const fn = x => x > -1
expect(all(fn)(list)).toBeTrue()
})
test('when false', () => {
const fn = x => x > 2
expect(all(fn, list)).toBeFalse()
})
```
</details>
[](#all)
### allFalse
```typescript
allFalse(...inputs: any[]): boolean
```
It returns `true` if all `inputs` arguments are falsy(empty objects and empty arrays are considered falsy).
Functions are valid inputs, but these functions cannot have their own arguments.
This method is very similar to `R.anyFalse`, `R.anyTrue` and `R.allTrue`
```javascript
R.allFalse(0, null, [], {}, '', () => false)
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.allFalse(0%2C%20null%2C%20%5B%5D%2C%20%7B%7D%2C%20''%2C%20()%20%3D%3E%20false)%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.allFalse</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.allFalse</strong> source</summary>
```javascript
import { isTruthy } from './_internals/isTruthy.js'
import { type } from './type.js'
export function allFalse(...inputs){
let counter = 0
while (counter < inputs.length){
const x = inputs[ counter ]
if (type(x) === 'Function'){
if (isTruthy(x())){
return false
}
} else if (isTruthy(x)){
return false
}
counter++
}
return true
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { runTests } from 'helpers-fn'
import { allFalse } from './allFalse.js'
const happy = { ok : [ () => false, () => [], () => {}, null, false, [] ] }
const withArray = { fail : [ ...happy.ok, [ 1 ] ] }
const withObject = { fail : [ ...happy.ok, { a : 1 } ] }
const withFunction = { fail : [ ...happy.ok, () => ({ a : 1 }) ] }
const withBoolean = { fail : [ ...happy.ok, true ] }
const testData = {
label : 'R.allFalse',
data : [ happy, withArray, withObject, withFunction, withBoolean ],
fn : input => allFalse(...input),
}
runTests(testData)
```
</details>
[](#allFalse)
### 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 this <strong>R.allPass</strong> example in Rambda REPL</a>
<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.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()
})
```
</details>
[](#allPass)
### allTrue
```typescript
allTrue(...input: any[]): boolean
```
It returns `true` if all `inputs` arguments are truthy(empty objects and empty arrays are considered falsy).
```javascript
R.allTrue(1, true, {a: 1}, [1], 'foo', () => true)
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.allTrue(1%2C%20true%2C%20%7Ba%3A%201%7D%2C%20%5B1%5D%2C%20'foo'%2C%20()%20%3D%3E%20true)%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.allTrue</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.allTrue</strong> source</summary>
```javascript
import { isFalsy } from './_internals/isFalsy.js'
import { type } from './type.js'
export function allTrue(...inputs){
let counter = 0
while (counter < inputs.length){
const x = inputs[ counter ]
if (type(x) === 'Function'){
if (isFalsy(x())){
return false
}
} else if (isFalsy(x)){
return false
}
counter++
}
return true
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { allTrue } from './allTrue.js'
test('with functions', () => {
const foo = () => 1
const bar = () => false
const baz = () => JSON.parse('{sda')
const result = allTrue(
foo, bar, baz
)
expect(result).toBeFalse()
})
test('usage with non boolean', () => {
const foo = { a : 1 }
const baz = [ 1, 2, 3 ]
const result = allTrue(
foo, foo, baz
)
expect(result).toBeTrue()
})
test('usage with boolean', () => {
const foo = 4
const baz = [ 1, 2, 3 ]
const result = allTrue(foo > 2, baz.length === 3)
expect(result).toBeTrue()
})
test('escapes early - case 0', () => {
const foo = undefined
const result = allTrue(foo, () => foo.a)
expect(result).toBeFalse()
})
test('escapes early - case 1', () => {
const foo = null
const result = allTrue(foo, () => foo.a)
expect(result).toBeFalse()
})
test('escapes early - case 2', () => {
const foo = { a : 'bar' }
const result = allTrue(
foo, foo.a, foo.a.b
)
expect(result).toBeFalse()
})
test('escapes early - case 3', () => {
const foo = { a : { b : 'foo' } }
const result = allTrue(
foo,
() => foo.a,
() => foo.a.b
)
expect(result).toBeTrue()
})
```
</details>
[](#allTrue)
### allType
```typescript
allType(targetType: RambdaTypes): (...input: any[]) => boolean
```
It returns a function which will return `true` if all of its `inputs` arguments belong to `targetType`.
> :boom: `targetType` is one of the possible returns of `R.type`
```javascript
const targetType = 'String'
const result = R.allType(
targetType
)('foo', 'bar', 'baz')
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20targetType%20%3D%20'String'%0A%0Aconst%20result%20%3D%20R.allType(%0A%20%20targetType%0A)('foo'%2C%20'bar'%2C%20'baz')%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.allType</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.allType</strong> source</summary>
```javascript
import { type } from './type.js'
export function allType(targetType){
return (...inputs) => {
let counter = 0
while (counter < inputs.length){
if (type(inputs[ counter ]) !== targetType){
return false
}
counter++
}
return true
}
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { allType } from './allType.js'
test('when true', () => {
const result = allType('Array')(
[ 1, 2, 3 ], [], [ null ]
)
expect(result).toBeTrue()
})
test('when false', () => {
const result = allType('String')(
1, undefined, null, []
)
expect(result).toBeFalse()
})
```
</details>
[](#allType)
### always
It returns function that always returns `x`.
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20fn%20%3D%20R.always(7)%0A%0Aconst%20result%20%3D%20fn()%0A%2F%2F%20%3D%3E%207">Try this <strong>R.always</strong> example in Rambda REPL</a>
[](#always)
### and
Logical AND
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?R.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%0Aconst%20result%20%3D%20R.and(true%2C%20'foo')%3B%20%2F%2F%20%3D%3E%20'foo'">Try this <strong>R.and</strong> example in Rambda REPL</a>
[](#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.
```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%20list%20%3D%20%5B1%2C%202%2C%203%5D%0Aconst%20predicate%20%3D%20x%20%3D%3E%20x%20*%20x%20%3E%208%0Aconst%20result%20%3D%20R.any(fn%2C%20list)%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.any</strong> example in Rambda REPL</a>
<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.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()
})
```
</details>
[](#any)
### anyFalse
```typescript
anyFalse(...input: any[]): boolean
```
It returns `true` if any of `inputs` is falsy(empty objects and empty arrays are considered falsy).
```javascript
R.anyFalse(1, {a: 1}, [1], () => false)
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.anyFalse(1%2C%20%7Ba%3A%201%7D%2C%20%5B1%5D%2C%20()%20%3D%3E%20false)%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.anyFalse</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.anyFalse</strong> source</summary>
```javascript
import { isFalsy } from './_internals/isFalsy.js'
import { type } from './type.js'
export function anyFalse(...inputs){
let counter = 0
while (counter < inputs.length){
const x = inputs[ counter ]
if (type(x) === 'Function'){
if (isFalsy(x())){
return true
}
} else if (isFalsy(x)){
return true
}
counter++
}
return false
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { anyFalse } from './anyFalse.js'
test('when true', () => {
expect(anyFalse(
true, true, false
)).toBeTruthy()
})
test('when false', () => {
expect(anyFalse(true, true)).toBeFalsy()
})
test('supports function', () => {
expect(anyFalse(
true,
() => true,
() => false
)).toBeTruthy()
})
```
</details>
[](#anyFalse)
### 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`.
```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 this <strong>R.anyPass</strong> example in Rambda REPL</a>
<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.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()
})
```
</details>
[](#anyPass)
### anyTrue
```typescript
anyTrue(...input: any[]): boolean
```
It returns `true` if any of `inputs` arguments are truthy(empty objects and empty arrays are considered falsy).
```javascript
R.anyTrue(0, null, [], {}, '', () => true)
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.anyTrue(0%2C%20null%2C%20%5B%5D%2C%20%7B%7D%2C%20''%2C%20()%20%3D%3E%20true)%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.anyTrue</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.anyTrue</strong> source</summary>
```javascript
import { isTruthy } from './_internals/isTruthy.js'
import { type } from './type.js'
export function anyTrue(...inputs){
let counter = 0
while (counter < inputs.length){
const x = inputs[ counter ]
if (type(x) === 'Function'){
if (isTruthy(x())){
return true
}
} else if (isTruthy(x)){
return true
}
counter++
}
return false
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { anyTrue } from './anyTrue.js'
test('when true', () => {
expect(anyTrue(
true, true, false
)).toBeTruthy()
})
test('when false', () => {
expect(anyTrue(
false, false, false
)).toBeFalsy()
})
test('supports function', () => {
expect(anyTrue(
false,
false,
false,
() => false,
() => true
)).toBeTruthy()
})
```
</details>
[](#anyTrue)
### anyType
```typescript
anyType(targetType: RambdaTypes): (...input: any[]) => boolean
```
It returns a function which will return `true` if at least one of its `inputs` arguments belongs to `targetType`.
`targetType` is one of the possible returns of `R.type`
> :boom: `targetType` is one of the possible returns of `R.type`
```javascript
const targetType = 'String'
const result = R.anyType(
targetType
)(1, {}, 'foo')
// => true
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20targetType%20%3D%20'String'%0A%0Aconst%20result%20%3D%20R.anyType(%0A%20%20targetType%0A)(1%2C%20%7B%7D%2C%20'foo')%0A%2F%2F%20%3D%3E%20true">Try this <strong>R.anyType</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.anyType</strong> source</summary>
```javascript
import { type } from './type.js'
export function anyType(targetType){
return (...inputs) => {
let counter = 0
while (counter < inputs.length){
if (type(inputs[ counter ]) === targetType){
return true
}
counter++
}
return false
}
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { anyType } from './anyType.js'
test('when true', () => {
const result = anyType('Array')(
1, undefined, null, []
)
expect(result).toBeTrue()
})
test('when false', () => {
const result = anyType('String')(
1, undefined, null, []
)
expect(result).toBeFalse()
})
```
</details>
[](#anyType)
### ap
```typescript
ap<T, U>(fns: Array<(a: T) => U>[], vs: T[]): U[]
```
It takes a list of functions and a list of values. Then it returns a list of values obtained by applying each function to each value.
```javascript
const result = R.ap(
[
x => x + 1,
x => x + 2,
],
[1, 2, 3]
)
// => [2, 3, 4, 3, 4, 5]
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.ap(%0A%20%20%5B%0A%20%20%20%20x%20%3D%3E%20x%20%2B%201%2C%0A%20%20%20%20x%20%3D%3E%20x%20%2B%202%2C%0A%20%20%5D%2C%0A%20%20%5B1%2C%202%2C%203%5D%0A)%0A%2F%2F%20%3D%3E%20%5B2%2C%203%2C%204%2C%203%2C%204%2C%205%5D">Try this <strong>R.ap</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.ap</strong> source</summary>
```javascript
export function ap(functions, input){
if (arguments.length === 1){
return _inputs => ap(functions, _inputs)
}
return functions.reduce((acc, fn) => [ ...acc, ...input.map(fn) ], [])
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { ap } from './ap.js'
function mult2(x){
return x * 2
}
function plus3(x){
return x + 3
}
test('happy', () => {
expect(ap([ mult2, plus3 ], [ 1, 2, 3 ])).toEqual([ 2, 4, 6, 4, 5, 6 ])
})
```
</details>
[](#ap)
### aperture
```typescript
aperture<N extends number, T>(n: N, list: T[]): Array<Tuple<T, N>> | []
```
It returns a new list, composed of consecutive `n`-tuples from a `list`.
```javascript
const result = R.aperture(2, [1, 2, 3, 4])
// => [[1, 2], [2, 3], [3, 4]]
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.aperture(2%2C%20%5B1%2C%202%2C%203%2C%204%5D)%0A%2F%2F%20%3D%3E%20%5B%5B1%2C%202%5D%2C%20%5B2%2C%203%5D%2C%20%5B3%2C%204%5D%5D">Try this <strong>R.aperture</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.aperture</strong> source</summary>
```javascript
export function aperture(step, list){
if (arguments.length === 1){
return _list => aperture(step, _list)
}
if (step > list.length) return []
let idx = 0
const limit = list.length - (step - 1)
const acc = new Array(limit)
while (idx < limit){
acc[ idx ] = list.slice(idx, idx + step)
idx += 1
}
return acc
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { aperture } from './aperture.js'
const list = [ 1, 2, 3, 4, 5, 6, 7 ]
test('happy', () => {
expect(aperture(1, list)).toEqual([ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ], [ 6 ], [ 7 ] ])
expect(aperture(2, list)).toEqual([
[ 1, 2 ],
[ 2, 3 ],
[ 3, 4 ],
[ 4, 5 ],
[ 5, 6 ],
[ 6, 7 ],
])
expect(aperture(3, list)).toEqual([
[ 1, 2, 3 ],
[ 2, 3, 4 ],
[ 3, 4, 5 ],
[ 4, 5, 6 ],
[ 5, 6, 7 ],
])
expect(aperture(8, list)).toEqual([])
})
```
</details>
[](#aperture)
### append
```typescript
append<T>(xToAppend: T, iterable: T[]): T[]
```
It adds element `x` at the end of `iterable`.
```javascript
const x = 'foo'
const result = R.append(x, ['bar', 'baz'])
// => ['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%20R.append(x%2C%20%5B'bar'%2C%20'baz'%5D)%0A%2F%2F%20%3D%3E%20%5B'bar'%2C%20'baz'%2C%20'foo'%5D">Try this <strong>R.append</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.append</strong> source</summary>
```javascript
import { cloneList } from './_internals/cloneList.js'
export function append(x, input){
if (arguments.length === 1) return _input => append(x, _input)
if (typeof input === 'string') return input.split('').concat(x)
const clone = cloneList(input)
clone.push(x)
return clone
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { append } from './append.js'
test('happy', () => {
expect(append('tests', [ 'write', 'more' ])).toEqual([
'write',
'more',
'tests',
])
})
test('append to empty array', () => {
expect(append('tests')([])).toEqual([ 'tests' ])
})
test('with strings', () => {
expect(append('o', 'fo')).toEqual([ 'f', 'o', 'o' ])
})
```
</details>
[](#append)
### 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.
```javascript
const result = R.apply(Math.max, [42, -Infinity, 1337])
// => 1337
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.apply(Math.max%2C%20%5B42%2C%20-Infinity%2C%201337%5D)%0A%2F%2F%20%3D%3E%201337">Try this <strong>R.apply</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.apply</strong> source</summary>
```javascript
export function apply(fn, args){
if (arguments.length === 1){
return _args => apply(fn, _args)
}
return fn.apply(this, args)
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { apply } from './apply.js'
import { bind } from './bind.js'
import { identity } from './identity.js'
test('happy', () => {
expect(apply(identity, [ 1, 2, 3 ])).toBe(1)
})
test('applies function to argument list', () => {
expect(apply(Math.max, [ 1, 2, 3, -99, 42, 6, 7 ])).toBe(42)
})
test('provides no way to specify context', () => {
const obj = {
method (){
return this === obj
},
}
expect(apply(obj.method, [])).toBeFalse()
expect(apply(bind(obj.method, obj), [])).toBeTrue()
})
```
</details>
[](#apply)
### applyDiff
```typescript
applyDiff<Output>(rules: ApplyDiffRule[], obj: object): Output
```
It changes paths in an object according to a list of operations. Valid operations are `add`, `update` and `delete`. Its use-case is while writing tests and you need to change the test data.
Note, that you cannot use `update` operation, if the object path is missing in the input object.
Also, you cannot use `add` operation, if the object path has a value.
```javascript
const obj = {a: {b:1, c:2}}
const rules = [
{op: 'remove', path: 'a.c'},
{op: 'add', path: 'a.d', value: 4},
{op: 'update', path: 'a.b', value: 2},
]
const result = R.applyDiff(rules, Record<string, unknown>)
const expected = {a: {b: 2, d: 4}}
// => `result` is equal to `expected`
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20obj%20%3D%20%7Ba%3A%20%7Bb%3A1%2C%20c%3A2%7D%7D%0Aconst%20rules%20%3D%20%5B%0A%20%20%7Bop%3A%20'remove'%2C%20path%3A%20'a.c'%7D%2C%0A%20%20%7Bop%3A%20'add'%2C%20path%3A%20'a.d'%2C%20value%3A%204%7D%2C%0A%20%20%7Bop%3A%20'update'%2C%20path%3A%20'a.b'%2C%20value%3A%202%7D%2C%0A%5D%0Aconst%20result%20%3D%20R.applyDiff(rules%2C%20Record%3Cstring%2C%20unknown%3E)%0Aconst%20expected%20%3D%20%7Ba%3A%20%7Bb%3A%202%2C%20d%3A%204%7D%7D%0A%0A%2F%2F%20%3D%3E%20%60result%60%20is%20equal%20to%20%60expected%60">Try this <strong>R.applyDiff</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.applyDiff</strong> source</summary>
```javascript
import { createPath } from './_internals/createPath.js'
import { assocPathFn } from './assocPath.js'
import { path as pathModule } from './path.js'
const ALLOWED_OPERATIONS = [ 'remove', 'add', 'update' ]
export function removeAtPath(path, obj){
const p = createPath(path)
const len = p.length
if (len === 0) return
if (len === 1) return delete obj[ p[ 0 ] ]
if (len === 2) return delete obj[ p[ 0 ] ][ p[ 1 ] ]
if (len === 3) return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ]
if (len === 4) return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ]
if (len === 5) return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ]
if (len === 6)
return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ]
if (len === 7)
return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ]
if (len === 8)
return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ][ p[ 7 ] ]
if (len === 9)
return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ][ p[ 7 ] ][ p[ 8 ] ]
if (len === 10)
return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ][ p[ 7 ] ][ p[ 8 ] ][
p[ 9 ]
]
}
export function applyDiff(rules, obj){
if (arguments.length === 1) return _obj => applyDiff(rules, _obj)
let clone = { ...obj }
rules.forEach(({ op, path, value }) => {
if (!ALLOWED_OPERATIONS.includes(op)) return
if (op === 'add' && path && value !== undefined){
if (pathModule(path, obj)) return
clone = assocPathFn(
path, value, clone
)
return
}
if (op === 'remove'){
if (pathModule(path, obj) === undefined) return
removeAtPath(path, clone)
return
}
if (op === 'update' && path && value !== undefined){
if (pathModule(path, obj) === undefined) return
clone = assocPathFn(
path, value, clone
)
}
})
return clone
}
```
</details>
<details>
<summary><strong>Tests</strong></summary>
```javascript
import { applyDiff } from './applyDiff.js'
test('remove operation', () => {
const rules = [
{
op : 'remove',
path : 'a.b',
},
]
const result = applyDiff(rules, {
a : {
b : 1,
c : 2,
},
})
expect(result).toEqual({ a : { c : 2 } })
})
test('update operation', () => {
const rules = [
{
op : 'update',
path : 'a.b',
value : 3,
},
{
op : 'update',
path : 'a.c.1',
value : 3,
},
{
op : 'update',
path : 'a.d',
value : 3,
},
]
expect(applyDiff(rules, {
a : {
b : 1,
c : [ 1, 2 ],
},
})).toEqual({
a : {
b : 3,
c : [ 1, 3 ],
},
})
})
test('add operation', () => {
const rules = [
{
op : 'add',
path : 'a.b',
value : 3,
},
{
op : 'add',
path : 'a.d',
value : 3,
},
]
const result = applyDiff(rules, {
a : {
b : 1,
c : 2,
},
})
expect(result).toEqual({
a : {
b : 1,
c : 2,
d : 3,
},
})
})
```
</details>
[](#applyDiff)
### applySpec
```typescript
applySpec<Spec extends Record<string, AnyFunction>>(
spec: Spec
): (
...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
```
> :boom: The currying in this function works best with functions with 4 arguments or less. (arity of 4)
```javascript
const fn = R.applySpec({
sum: R.add,
nested: { mul: R.multiply }
})
const result = fn(2, 4)
// => { sum: 6, nested: { mul: 8 } }
```
<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20fn%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)%0Aconst%20result%20%3D%20fn(2%2C%204)%20%0A%2F%2F%20%3D%3E%20%7B%20sum%3A%206%2C%20nested%3A%20%7B%20mul%3A%208%20%7D%20%7D">Try this <strong>R.applySpec</strong> example in Rambda REPL</a>
<details>
<summary><strong>R.applySpec</strong> source</summary>
```javascript
import { isArray } from './_internals/isArray.js'
// recursively traverse the given spec object to find the highest arity function
export function __findHighestArity(spec, max = 0){
for (const key in spec){
if (spec.hasOwnProperty(key) === false || key === 'constructor') continue
if (typeof spec[ key ] === 'object'){
max = Math.max(max, __findHighestArity(spec[ key ]))
}
if (typeof spec[ key ] === 'function'){
max = Math.max(max, spec[ key ].length)
}
}
return max
}
function __filterUndefined(){
const defined = []
let i = 0
const l = arguments.length
while (i < l){
if (typeof arguments[ i ] === 'undefined') break
defined[ i ] = arguments[ i ]
i++
}
return defined
}
function __applySpecWithArity(
spec, arity, cache
){
const remaining = arity - cache.length
if (remaining === 1)
return x =>
__applySpecWithArity(
spec, arity, __filterUndefined(...cache, x)
)
if (remaining === 2)
return (x, y) =>
__applySpecWithArity(
spec, arity, __filterUndefined(
...cache, x, y
)
)
if (remaining === 3)
return (
x, y, z
) =>
__applySpecWithArity(
spec, arity, __filterUndefined(
...cache, x, y, z
)
)
if (remaining === 4)
return (
x, y, z, a
) =>
__applySpecWithArity(
spec,
arity,
__filterUndefined(
...cache, x, y, z, a
)
)
if (remaining > 4)
return (...args) =>
__applySpecWithArity(
spec, arity, __filterUndefined(...cache, ...args)
)
// handle spec as Array
if (isArray(spec)){
const ret = []
let i = 0
const l = spec.length
for (; i < l; i++){
// handle recursive spec inside array
if (typeof spec[ i ] === 'object' || isArray(spec[ i ])){
ret[ i ] = __applySpecWithArity(
spec[ i ], arity, cache
)
}
// apply spec to the key
if (typeof spec[ i ] === 'function'){
ret[ i ] = spec[ i ](...cache)
}
}
return ret
}
// handle spec as Object
const ret = {}
// apply callbacks to each property in the spec object
for (const key in spec){
if (spec.hasOwnProperty(key) === false || key === 'constructor') continue
// apply the spec recursively
if (typeof spec[ key ] === 'object'){
ret[ key ] = __applySpecWithArity(
spec[ key ], arity, cache
)
continue
}
// apply spec to the key
if (typeof spec[ key ] === 'function'){
ret[ key ] = spec[ key ](...cache)
}
}
return ret
}
export function applySpec(spec, ...args){
// get the highest arity spec function, cache the result and pass to __applySpecWithArity
const arity = __findHighestArity(spec)
if (arity === 0){
return () => ({})
}
const toReturn = __applySpecWithArity(
spec, arity, args
)
return toReturn
}
```
</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.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 : {