UNPKG

rambdax

Version:

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

1,946 lines (1,425 loc) 498 kB
# 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/#/) [![install size](https://packagephobia.com/badge?p=rambdax)](https://packagephobia.com/result?p=rambdax) [![GitHub contributors](https://img.shields.io/github/contributors/selfrefactor/rambdax.svg)](https://github.com/selfrefactor/rambdax/graphs/contributors) [![codecov](https://codecov.io/gh/selfrefactor/rambda/branch/master/graph/badge.svg)](https://codecov.io/gh/selfrefactor/rambda) ![Library size](https://img.shields.io/bundlephobia/minzip/rambdax) ## ❯ 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. [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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) [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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. [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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"; ``` [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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. [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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) [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#addIndex) ### addIndexRight Same as `R.addIndex`, but it will passed indexes are decreasing, instead of increasing. [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#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 : {