UNPKG

rambda

Version:

Lightweight and faster alternative to Ramda with included TS definitions

2,015 lines (1,538 loc) 442 kB
# Rambda `Rambda` is smaller and faster alternative to the popular functional programming library **Ramda**. - [Documentation](https://selfrefactor.github.io/rambda/#/) [![CircleCI](https://circleci.com/gh/selfrefactor/rambda/tree/master.svg?style=svg)](https://circleci.com/gh/selfrefactor/rambda/tree/master) [![codecov](https://codecov.io/gh/selfrefactor/rambda/branch/master/graph/badge.svg)](https://codecov.io/gh/selfrefactor/rambda) ![Commit activity](https://img.shields.io/github/commit-activity/y/selfrefactor/rambda) ![All contributors](https://img.shields.io/github/contributors/selfrefactor/rambda) ![Library size](https://img.shields.io/bundlephobia/minzip/rambda) [![install size](https://packagephobia.com/badge?p=rambda)](https://packagephobia.com/result?p=rambda) [![nest badge](https://nest.land/badge.svg)](https://nest.land/package/rambda) [![HitCount](https://hits.dwyl.com/selfrefactor/rambda.svg?style=flat-square)](http://hits.dwyl.com/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> * [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) ## ❯ Rambda's advantages ### TypeScript included TypeScript definitions are included in the library, in comparison to **Ramda**, where you need to additionally install `@types/ramda`. Still, you need to be aware that functional programming features in `TypeScript` are in development, which means that using **R.compose/R.pipe** can be problematic. Important - Rambda version `7.1.0`(or higher) requires TypeScript version `4.3.3`(or higher). #### Immutable TS definitions You can use immutable version of Rambda definitions, which is linted with ESLint `functional/prefer-readonly-type` plugin. ``` import {add} from 'rambda/immutable' ``` ### Deno support Latest version of **Ramba** available for `Deno` users is 3 years old. This is not the case with **Rambda** as most of recent releases are available for `Deno` users. Also, `Rambda` provides you with included TS definitions: ``` // Deno extension(https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno) // is installed and initialized import * as R from "https://deno.land/x/rambda/mod.ts"; import * as Ramda from "https://x.nest.land/ramda@0.27.2/mod.ts"; R.add(1)('foo') // => will trigger warning in VSCode as it should Ramda.add(1)('foo') // => will not trigger warning in VSCode ``` ### Dot notation for `R.path`, `R.paths`, `R.assocPath` and `R.lensPath` Standard usage of `R.path` is `R.path(['a', 'b'], {a: {b: 1} })`. In **Rambda** you have the choice to use dot notation(which is arguably more readable): ``` R.path('a.b', {a: {b: 1} }) ``` ### Comma notation for `R.pick` and `R.omit` Similar to dot notation, but the separator is comma(`,`) instead of dot(`.`). ``` R.pick('a,b', {a: 1 , b: 2, c: 3} }) // No space allowed between properties ``` ### Speed **Rambda** is generally more performant than `Ramda` as the [benchmarks](#-benchmarks) can prove that. ### Support One of the main issues with `Ramda` is the slow process of releasing new versions. This is not the case with **Rambda** as releases are made on regular basis. [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-rambdas-advantages) ## ❯ Missing Ramda methods <details> <summary> Click to see the full list of 0 Ramda methods not implemented in Rambda and their status. </summary> - dropRepeatsBy - empty - eqBy - forEachObjIndexed - gt - gte - hasIn - innerJoin - insert - insertAll - into - invert - invertObj - invoker - isNotNil - keysIn - lift - liftN - lt - lte - mapAccum - mapAccumRight - memoizeWith - mergeDeepLeft - mergeDeepWith - mergeDeepWithKey - mergeWithKey - nAry - nthArg - o - otherwise - pair - partialRight - pathSatisfies - pickBy - pipeWith - project - promap - reduceBy - reduceRight - reduceWhile - reduced - remove - scan - sequence - sortWith - splitWhenever - swap - symmetricDifferenceWith - andThen - toPairsIn - unary - uncurryN - unfold - unionWith - until - useWith - valuesIn - xprod - thunkify - default Most of above methods are in progress to be added to **Rambda**. The following methods are not going to be added: - __ - 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. - construct - Using classes is not very functional programming oriented. - constructN - same as above - transduce - currently is out of focus - traverse - same as above </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-missing-ramda-methods) ## ❯ Install - **yarn add rambda** - For UMD usage either use `./dist/rambda.umd.js` or the following CDN link: ``` https://unpkg.com/rambda@CURRENT_VERSION/dist/rambda.umd.js ``` - with deno ``` import {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. > 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) [![---------------](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.29.0). </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`. <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 [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#addIndex) ### addIndexRight [![---------------](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])`. <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>All TypeScript definitions</summary> ```typescript adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[]; adjust<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[]; ``` </details> <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. <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>All TypeScript definitions</summary> ```typescript all<T>(predicate: (x: T) => boolean, list: T[]): boolean; all<T>(predicate: (x: T) => boolean): (list: 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 ])) 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> <details> <summary><strong>TypeScript</strong> test</summary> ```typescript import {all} from 'rambda' describe('all', () => { it('happy', () => { const result = all( x => { x // $ExpectType number return x > 0 }, [1, 2, 3] ) result // $ExpectType boolean }) it('curried needs a type', () => { const result = all<number>(x => { x // $ExpectType number return x > 0 })([1, 2, 3]) result // $ExpectType boolean }) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#all) ### 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. <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>All TypeScript definitions</summary> ```typescript allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean; allPass<T>(predicates: ((...inputs: T[]) => boolean)[]): (...inputs: 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.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> <details> <summary><strong>TypeScript</strong> test</summary> ```typescript import {allPass, filter} from 'rambda' describe('allPass', () => { it('happy', () => { const x = allPass<number>([ y => { y // $ExpectType number return typeof y === 'number' }, y => { return y > 0 }, ])(11) x // $ExpectType boolean }) it('issue #642', () => { const isGreater = (num: number) => num > 5 const pred = allPass([isGreater]) const xs = [0, 1, 2, 3] const filtered1 = filter(pred)(xs) filtered1 // $ExpectType number[] const filtered2 = xs.filter(pred) filtered2 // $ExpectType number[] }) it('issue #604', () => { const plusEq = function(w: number, x: number, y: number, z: number) { return w + x === y + z } const result = allPass([plusEq])(3, 3, 3, 3) result // $ExpectType boolean }) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#allPass) ### 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. <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>All TypeScript definitions</summary> ```typescript any<T>(predicate: (x: T) => boolean, list: T[]): boolean; any<T>(predicate: (x: T) => boolean): (list: 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.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> <details> <summary><strong>TypeScript</strong> test</summary> ```typescript import {any} from 'rambda' describe('R.any', () => { it('happy', () => { const result = any( x => { x // $ExpectType number return x > 2 }, [1, 2, 3] ) result // $ExpectType boolean }) it('when curried needs a type', () => { const result = any<number>(x => { x // $ExpectType number return x > 2 })([1, 2, 3]) result // $ExpectType boolean }) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#any) ### anyPass ```typescript anyPass<T, U extends T[]>(predicates: { [K in keyof U]: (x: T) => x is U[K] ``` 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`. <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>All TypeScript definitions</summary> ```typescript anyPass<T, U extends T[]>(predicates: { [K in keyof U]: (x: T) => x is U[K]; }): (input: T) => input is U[number]; anyPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean; anyPass<T>(predicates: ((...inputs: T[]) => boolean)[]): (...inputs: T[]) => boolean; ``` </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.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> <details> <summary><strong>TypeScript</strong> test</summary> ```typescript import {anyPass, filter} from 'rambda' describe('anyPass', () => { it('happy', () => { const x = anyPass<number>([ y => { y // $ExpectType number return typeof y === 'number' }, y => { return y > 0 }, ])(11) x // $ExpectType boolean }) it('issue #604', () => { const plusEq = function(w: number, x: number, y: number, z: number) { return w + x === y + z } const result = anyPass([plusEq])(3, 3, 3, 3) result // $ExpectType boolean }) it('issue #642', () => { const isGreater = (num: number) => num > 5 const pred = anyPass([isGreater]) const xs = [0, 1, 2, 3] const filtered1 = filter(pred)(xs) filtered1 // $ExpectType number[] const filtered2 = xs.filter(pred) filtered2 // $ExpectType number[] }) it('functions as a type guard', () => { const isString = (x: unknown): x is string => typeof x === 'string'; const isNumber = (x: unknown): x is number => typeof x === 'number'; const isBoolean = (x: unknown): x is boolean => typeof x === 'boolean'; const isStringNumberOrBoolean = anyPass([isString, isNumber, isBoolean]); isStringNumberOrBoolean // $ExpectType (input: unknown) => input is string | number | boolean const aValue: unknown = 1; if (isStringNumberOrBoolean(aValue)) { aValue // $ExpectType string | number | boolean } }) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#anyPass) ### ap ```typescript ap<T, U>(fns: Array<(a: T) => U>[], vs: T[]): U[] ``` <details> <summary>All TypeScript definitions</summary> ```typescript ap<T, U>(fns: Array<(a: T) => U>[], vs: T[]): U[]; ap<T, U>(fns: Array<(a: T) => U>): (vs: T[]) => U[]; ap<R, A, B>(fn: (r: R, a: A) => B, fn1: (r: R) => A): (r: R) => B; ``` </details> <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>> | [] ``` <details> <summary>All TypeScript definitions</summary> ```typescript aperture<N extends number, T>(n: N, list: T[]): Array<Tuple<T, N>> | []; aperture<N extends number>(n: N): <T>(list: T[]) => Array<Tuple<T, N>> | []; ``` </details> <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`. <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>All TypeScript definitions</summary> ```typescript append<T>(xToAppend: T, iterable: T[]): T[]; append<T, U>(xToAppend: T, iterable: IsFirstSubtypeOfSecond<T, U>[]) : U[]; append<T>(xToAppend: T): <U>(iterable: IsFirstSubtypeOfSecond<T, U>[]) => U[]; append<T>(xToAppend: T): (iterable: T[]) => T[]; ``` </details> <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' import { append } from 'ramda' 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> <details> <summary><strong>TypeScript</strong> test</summary> ```typescript import {append, prepend} from 'rambda' const listOfNumbers = [1, 2, 3] const listOfNumbersAndStrings = [1, 'b', 3] describe('R.append/R.prepend', () => { describe("with the same primitive type as the array's elements", () => { it('uncurried', () => { // @ts-expect-error append('d', listOfNumbers) // @ts-expect-error prepend('d', listOfNumbers) append(4, listOfNumbers) // $ExpectType number[] prepend(4, listOfNumbers) // $ExpectType number[] }) it('curried', () => { // @ts-expect-error append('d')(listOfNumbers) append(4)(listOfNumbers) // $ExpectType number[] prepend(4)(listOfNumbers) // $ExpectType number[] }) }) describe("with a subtype of the array's elements", () => { it('uncurried', () => { // @ts-expect-error append(true, listOfNumbersAndStrings) append(4, listOfNumbersAndStrings) // $ExpectType (string | number)[] prepend(4, listOfNumbersAndStrings) // $ExpectType (string | number)[] }) it('curried', () => { // @ts-expect-error append(true)(listOfNumbersAndStrings) append(4)(listOfNumbersAndStrings) // $ExpectType (string | number)[] prepend(4)(listOfNumbersAndStrings) // $ExpectType (string | number)[] }) }) describe("expanding the type of the array's elements", () => { it('uncurried', () => { // @ts-expect-error append('d', listOfNumbers) append<string | number>('d', listOfNumbers) // $ExpectType (string | number)[] prepend<string | number>('d', listOfNumbers) // $ExpectType (string | number)[] }) it('curried', () => { // @ts-expect-error append('d')(listOfNumbers) const appendD = append('d') appendD<string | number>(listOfNumbers) // $ExpectType (string | number)[] const prependD = prepend('d') prependD<string | number>(listOfNumbers) // $ExpectType (string | number)[] }) }) }) ``` </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. <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>All TypeScript definitions</summary> ```typescript apply<T = any>(fn: (...args: any[]) => T, args: any[]): T; apply<T = any>(fn: (...args: any[]) => T): (args: any[]) => T; ``` </details> <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> <details> <summary><strong>TypeScript</strong> test</summary> ```typescript import {apply, identity} from 'rambda' describe('R.apply', () => { it('happy', () => { const result = apply<number>(identity, [1, 2, 3]) result // $ExpectType number }) it('curried', () => { const fn = apply<number>(identity) const result = fn([1, 2, 3]) result // $ExpectType number }) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#apply) ### applySpec ```typescript applySpec<Spec extends Record<string, AnyFunction>>( spec: Spec ): ( ...args: Parameters<ValueOfRecord<Spec>> ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> } ``` <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>All TypeScript definitions</summary> ```typescript applySpec<Spec extends Record<string, AnyFunction>>( spec: Spec ): ( ...args: Parameters<ValueOfRecord<Spec>> ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }; applySpec<T>(spec: any): (...args: unknown[]) => T; ``` </details> <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 : { sum : add }, })(1, 2) const expected = { unnested : 0, nested : { sum : 3 }, } expect(result).toEqual(expected) }) test('works with arrays of nested specs', () => { const result = applySpec({ unnested : always(0), nested : [ { sum : add } ], })(1, 2) expect(result).toEqual({ unnested : 0, nested : [ { sum : 3 } ], }) }) test('works with arrays of spec objects', () => { const result = applySpec([ { sum : add } ])(1, 2) expect(result).toEqual([ { sum : 3 } ]) }) test('works with arrays of functions', () => { const result = applySpec([ map(prop('a')), map(prop('b')) ])([ { a : 'a1', b : 'b1', }, { a : 'a2', b : 'b2', }, ]) const expected = [ [ 'a1', 'a2' ], [ 'b1', 'b2' ], ] expect(result).toEqual(expected) }) test('works with a spec defining a map key', () => { expect(applySpec({ map : prop('a') })({ a : 1 })).toEqual({ map : 1 }) }) test('cannot retains the highest arity', () => { const f = applySpec({ f1 : nAry(2, T), f2 : nAry(5, T), }) const fRamda = applySpecRamda({ f1 : nAry(2, T), f2 : nAry(5, T), }) expect(f).toHaveLength(0) expect(fRamda).toHaveLength(5) }) test('returns a curried function', () => { expect(applySpec({ sum : add })(1)(2)).toEqual({ sum : 3 }) }) // Additional tests // ============================================ test('arity', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, } expect(applySpec( spec, 1, 2, 3 )).toEqual({ one : 1, two : 3, three : 6, }) }) test('arity over 5 arguments', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, four : ( x1, x2, x3, x4 ) => x1 + x2 + x3 + x4, five : ( x1, x2, x3, x4, x5 ) => x1 + x2 + x3 + x4 + x5, } expect(applySpec( spec, 1, 2, 3, 4, 5 )).toEqual({ one : 1, two : 3, three : 6, four : 10, five : 15, }) }) test('curried', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, } expect(applySpec(spec)(1)(2)(3)).toEqual({ one : 1, two : 3, three : 6, }) }) test('curried over 5 arguments', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, four : ( x1, x2, x3, x4 ) => x1 + x2 + x3 + x4, five : ( x1, x2, x3, x4, x5 ) => x1 + x2 + x3 + x4 + x5, } expect(applySpec(spec)(1)(2)(3)(4)(5)).toEqual({ one : 1, two : 3, three : 6, four : 10, five : 15, }) }) test('undefined property', () => { const spec = { prop : path([ 'property', 'doesnt', 'exist' ]) } expect(applySpec(spec, {})).toEqual({ prop : undefined }) }) test('restructure json object', () => { const spec = { id : path('user.id'), name : path('user.firstname'), profile : path('user.profile'), doesntExist : path('user.profile.doesntExist'), info : { views : compose(inc, prop('views')) }, type : always('playa'), } const data = { user : { id : 1337, firstname : 'john', lastname : 'shaft', profile : 'shaft69', }, views : 42, } expect(applySpec(spec, data)).toEqual({ id : 1337, name : 'john', profile : 'shaft69', doesntExist : undefined, info : { views : 43 }, type : 'playa', }) }) ``` </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> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#applySpec) ### applyTo [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#applyTo) ### ascend [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#ascend) ### assoc It makes a shallow clone of `obj` with setting or overriding the property `prop` with `newValue`. <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%20%3D%3E%20%7Ba%3A%201%2C%20b%3A%202%2C%20c%3A%203%7D">Try this <strong>R.assoc</strong> example in Rambda REPL</a> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#assoc) ### assocPath ```typescript assocPath<Output>(path: Path, newValue: any, obj: object): Output ``` It makes a shallow clone of `obj` with setting or overriding with `newValue` the property found with `path`. <a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20path%20%3D%20'b.c'%0Aconst%20newValue%20%3D%202%0Aconst%20obj%20%3D%20%7B%20a%3A%201%20%7D%0A%0Aconst%20result%20%3D%20R.assocPath(path%2C%20newValue%2C%20Record%3Cstring%2C%20unknown%3E)%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 this <strong>R.assocPath</strong> example in Rambda REPL</a> <details> <summary>All TypeScript definitions</summary> ```typescript assocPath<Output>(path: Path, newValue: any, obj: object): Output; assocPath<Output>(path: Path, newValue: any): (obj: object) => Output; assocPath<Output>(path: Path): (newValue: any) => (obj: object) => Output; ``` </details> <details> <summary><strong>R.assocPath</strong> source</summary> ```javascript import { cloneList } from './_internals/cloneList.js' import { createPath } from './_internals/createPath.js' import { isArray } from './_internals/isArray.js' import { isIndexInteger } from './_internals/isInteger.js' import { assocFn } from './assoc.js' import { curry } from './curry.js' export function assocPathFn( path, newValue, input ){ const pathArrValue = createPath(path) if (pathArrValue.length === 0) return newValue const index = pathArrValue[ 0 ] if (pathArrValue.length > 1){ const condition = typeof input !== 'object' || input === null || !input.hasOwnProperty(index) const nextInput = condition ? isIndexInteger(pathArrValue[ 1 ]) ? [] : {} : input[ index ] newValue = assocPathFn( Array.prototype.slice.call(pathArrValue, 1), newValue, nextInput ) } if (isIndexInteger(index) && isArray(input)){ const arr = cloneList(input) arr[ index ] = newValue return arr } return assocFn( index, newValue, input ) } export const assocPath = curry(assocPathFn) ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { assocPathFn } from './assocPath.js' test.only('happy', () => { const path = 'a.c.1' const input = { a : { b : 1, c : [ 1, 2 ], }, } assocPathFn( path, 3, input ) expect(input).toEqual({ a : { b : 1, c : [ 1, 2 ], }, }) }) test('string can be used as path input', () => { const testObj = { a : [ { b : 1 }, { b : 2 } ], d : 3, } const result1 = assocPathFn( [ 'a', 0, 'b' ], 10, testObj ) const result2 = assocPathFn( 'a.0.b', 10, testObj ) const expected = { a : [ { b : 10 }, { b : 2 } ], d : 3, } expect(result1).toEqual(expected) expect(result2).toEqual(expected) }) test('difference with ramda - doesn\'t overwrite primitive values with keys in the path', () => { const obj = { a : 'str' } const result = assocPath( [ 'a', 'b' ], 42, obj ) expect(result).toEqual({ a : { 0 : 's', 1 : 't', 2 : 'r', b : 42, }, }) }) test('bug', () => { /* https://github.com/selfrefactor/rambda/issues/524 */ const state = {} const withDateLike = assocPath( [ 'outerProp', '2020-03-10' ], { prop : 2 }, state ) const withNumber = assocPath( [ 'outerProp', '5' ], { prop : 2 }, state ) const withDateLikeExpected = { outerProp : { '2020-03-10' : { prop : 2 } } } const withNumberExpected = { outerProp : { 5 : { prop : 2 } } } expect(withDateLike).toEqual(withDateLikeExpected) expect(withNumber).toEqual(withNumberExpected) }) test('adds a key to an empty object', () => { expect(assocPath( [ 'a' ], 1, {} )).toEqual({ a : 1 }) }) test('adds a key to a non-empty object', () => { expect(assocPath( 'b', 2, { a : 1 } )).toEqual({ a : 1, b : 2, }) }) test('adds a nested key to a non-empty object', () => { expect(assocPath( 'b.c', 2, { a : 1 } )).toEqual({ a : 1, b : { c : 2 }, }) }) test('adds a nested key to a nested non-empty object - curry case 1', () => { expect(assocPath('b.d', 3)({ a : 1, b : { c : 2 }, })).toEqual({ a : 1, b : { c : 2, d : 3, }, }) }) test('adds a key to a non-empty object - curry case 1', () => { expect(assocPath('b', 2)({ a : 1 })).toEqual({ a : 1, b : 2, }) }) test('adds a nested key to a non-empty object - curry case 1', () => { expect(assocPath('b.c', 2)({ a : 1 })).toEqual({ a : 1, b : { c : 2 }, }) }) test('adds a key to a non-empty object - curry case 2', () => { expect(assocPath('b')(2, { a : 1 })).toEqual({ a : 1, b : 2, }) }) test('adds a key to a non-empty object - curry case 3', () => { const result = assocPath('b')(2)({ a : 1 }) expect(result).toEqual({ a : 1, b : 2, }) }) test('changes an existing key', () => { expect(assocPath( 'a', 2, { a : 1 }