UNPKG

rambdax

Version:

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

2,158 lines (1,628 loc) 495 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/#/) [![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) [![dependencies Status](https://david-dm.org/selfrefactor/rambdax/status.svg)](https://david-dm.org/selfrefactor/rambdax) ![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 ### 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`. ### Support Most of the valid issues are fixed within 2-3 days. Closing the issue is usually accompanied by publishing a new patch version of `Rambdax` to NPM. [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-rambdaxs-advantages) ## ❯ Missing Ramda methods <details> <summary> Click to see the full list of 90 Ramda methods not implemented in Rambda </summary> - __ - addIndex - ap - aperture - apply - applyTo - ascend - binary - bind - call - comparator - composeK - composeP - composeWith - construct - constructN - contains - countBy - descend - differenceWith - dissocPath - empty - eqBy - forEachObjIndexed - gt - gte - hasIn - innerJoin - insert - insertAll - into - invert - invertObj - invoker - juxt - keysIn - lift - liftN - lt - lte - mapAccum - mapAccumRight - mapObjIndexed - memoizeWith - mergeDeepLeft - mergeDeepWith - mergeDeepWithKey - mergeRight - mergeWith - mergeWithKey - nAry - nthArg - o - objOf - otherwise - pair - partialRight - pathSatisfies - pickBy - pipeK - pipeP - pipeWith - project - propSatisfies - reduceBy - reduceRight - reduceWhile - reduced - remove - scan - sequence - sortWith - symmetricDifferenceWith - andThen - toPairsIn - transduce - traverse - unapply - unary - uncurryN - unfold - unionWith - uniqBy - unnest - until - useWith - valuesIn - xprod - thunkify - default </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 {compose, add} from 'https://raw.githubusercontent.com/selfrefactor/rambdax/master/dist/rambdax.esm.js' ``` [![---------------](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. - 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.20) and *Ramda*(0.27.1). </summary> method | Rambda | Ramda | Lodash --- |--- | --- | --- *add* | 96.25% slower | 96.24% slower | 🚀 Fastest *adjust* | 🚀 Fastest | 5.52% slower | 🔳 *all* | 🚀 Fastest | 94.95% slower | 🔳 *allPass* | 🚀 Fastest | 98.95% slower | 🔳 *any* | 🚀 Fastest | 98.18% slower | 6.18% slower *anyPass* | 🚀 Fastest | 99.09% slower | 🔳 *append* | 🚀 Fastest | 84.09% slower | 🔳 *applySpec* | 🚀 Fastest | 75.73% slower | 🔳 *assoc* | 87.98% slower | 57.39% slower | 🚀 Fastest *clone* | 🚀 Fastest | 96.03% slower | 91.75% slower *compose* | 🚀 Fastest | 96.45% slower | 77.83% slower *converge* | 49.12% slower | 🚀 Fastest | 🔳 *curry* | 🚀 Fastest | 34.9% slower | 🔳 *curryN* | 63.32% slower | 🚀 Fastest | 🔳 *defaultTo* | 🚀 Fastest | 50.3% slower | 🔳 *drop* | 🚀 Fastest | 97.45% slower | 🔳 *dropLast* | 🚀 Fastest | 97.07% slower | 🔳 *equals* | 72.11% slower | 79.48% slower | 🚀 Fastest *filter* | 🚀 Fastest | 94.74% slower | 58.18% slower *find* | 🚀 Fastest | 98.2% slower | 88.96% slower *findIndex* | 🚀 Fastest | 97.97% slower | 79.39% slower *flatten* | 6.56% slower | 95.38% slower | 🚀 Fastest *ifElse* | 🚀 Fastest | 70.97% slower | 🔳 *includes* | 🚀 Fastest | 71.7% slower | 🔳 *indexOf* | 🚀 Fastest | 84.08% slower | 7.86% slower *init* | 94.42% slower | 97.55% slower | 🚀 Fastest *is* | 🚀 Fastest | 11.72% slower | 🔳 *isEmpty* | 51.68% slower | 93.82% slower | 🚀 Fastest *last* | 🚀 Fastest | 99.64% slower | 1.05% slower *lastIndexOf* | 🚀 Fastest | 42.38% slower | 🔳 *map* | 🚀 Fastest | 69.63% slower | 4.68% slower *match* | 🚀 Fastest | 46.75% slower | 🔳 *merge* | 63.55% slower | 🚀 Fastest | 55.25% slower *none* | 🚀 Fastest | 98.22% slower | 🔳 *omit* | 🚀 Fastest | 70.66% slower | 97.56% slower *over* | 🚀 Fastest | 50.77% slower | 🔳 *path* | 🚀 Fastest | 74.94% slower | 5.72% slower *pick* | 🚀 Fastest | 26.29% slower | 86.82% slower *prop* | 🚀 Fastest | 89.89% slower | 🔳 *propEq* | 🚀 Fastest | 95.26% slower | 🔳 *range* | 95.17% slower | 90.22% slower | 🚀 Fastest *reduce* | 52.76% slower | 74.02% slower | 🚀 Fastest *repeat* | 85.91% slower | 95.31% slower | 🚀 Fastest *replace* | 0.47% slower | 28.13% slower | 🚀 Fastest *set* | 🚀 Fastest | 36.26% slower | 🔳 *sort* | 🚀 Fastest | 63.15% slower | 🔳 *sortBy* | 🚀 Fastest | 61.57% slower | 88.88% slower *split* | 🚀 Fastest | 85.34% slower | 33.69% slower *splitEvery* | 🚀 Fastest | 90.18% slower | 🔳 *take* | 93.44% slower | 98.04% slower | 🚀 Fastest *takeLast* | 92.61% slower | 98.83% slower | 🚀 Fastest *test* | 🚀 Fastest | 94.42% slower | 🔳 *type* | 18.91% slower | 🚀 Fastest | 🔳 *uniq* | 98.98% slower | 96.58% slower | 🚀 Fastest *update* | 🚀 Fastest | 38.88% slower | 🔳 *view* | 🚀 Fastest | 82.21% slower | 🔳 </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#-benchmarks) ## ❯ Used by - [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 intergration](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 ```typescript add(a: number, b: number): number ``` It adds `a` and `b`. > :boom: It doesn't work with strings, as the inputs are parsed to numbers before calculation. ```javascript R.add(2, 3) // => 5 ``` <a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.add(2%2C%203)%20%2F%2F%20%3D%3E%20%205">Try this <strong>R.add</strong> example in Rambda REPL</a> <details> <summary><strong>R.add</strong> source</summary> ```javascript export function add(a, b){ if (arguments.length === 1) return _b => add(a, _b) return Number(a) + Number(b) } ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { add } from './add' test('with number', () => { expect(add(2, 3)).toEqual(5) expect(add(7)(10)).toEqual(17) }) test('string is bad input', () => { expect(add('foo', 'bar')).toBeNaN() }) test('ramda specs', () => { expect(add('1', '2')).toEqual(3) expect(add(1, '2')).toEqual(3) expect(add(true, false)).toEqual(1) expect(add(null, null)).toEqual(0) expect(add(undefined, undefined)).toEqual(NaN) expect(add(new Date(1), new Date(2))).toEqual(3) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#add) ### adjust ```typescript adjust<T>(index: number, replaceFn: (x: T) => T, list: readonly T[]): readonly T[] ``` It replaces `index` in array `list` with the result of `replaceFn(list[i])`. ```javascript R.adjust( 0, a => a + 1, [0, 100] ) // => [1, 100] ``` <a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.adjust(%0A%20%200%2C%0A%20%20a%20%3D%3E%20a%20%2B%201%2C%0A%20%20%5B0%2C%20100%5D%0A)%20%2F%2F%20%3D%3E%20%5B1%2C%20100%5D">Try this <strong>R.adjust</strong> example in Rambda REPL</a> <details> <summary><strong>R.adjust</strong> source</summary> ```javascript import { curry } from './curry' function adjustFn( index, replaceFn, list ){ const actualIndex = index < 0 ? list.length + index : index if (index >= list.length || actualIndex < 0) return list const clone = list.slice() clone[ actualIndex ] = replaceFn(clone[ actualIndex ]) return clone } export const adjust = curry(adjustFn) ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { add } from './add' import { adjust } from './adjust' import { pipe } from './pipe' 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: readonly 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' 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: readonly 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' import { type } from './type' 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' 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: readonly ((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' test('happy', () => { const rules = [ x => typeof x === 'number', x => x > 10, x => x * 7 < 100 ] expect(allPass(rules)(11)).toBeTrue() expect(allPass(rules)(undefined)).toBeFalse() }) test('when returns true', () => { const conditionArr = [ val => val.a === 1, val => val.b === 2 ] expect(allPass(conditionArr)({ a : 1, b : 2, })).toBeTrue() }) test('when returns false', () => { const conditionArr = [ val => val.a === 1, val => val.b === 3 ] expect(allPass(conditionArr)({ a : 1, b : 2, })).toBeFalse() }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#allPass) ### allTrue ```typescript allTrue(...input: readonly 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' import { type } from './type' 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' 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: readonly 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' 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' 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 ```typescript always<T>(x: T): () => T ``` It returns function that always returns `x`. ```javascript const fn = R.always(7) console.log(fn())// => 7 ``` <a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20fn%20%3D%20R.always(7)%0A%0Aconsole.log(fn())%2F%2F%20%3D%3E%207">Try this <strong>R.always</strong> example in Rambda REPL</a> <details> <summary><strong>R.always</strong> source</summary> ```javascript export function always(x){ return () => x } ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { always } from './always' import { F } from './F' test('happy', () => { const fn = always(7) expect(fn()).toEqual(7) expect(fn()).toEqual(7) }) test('f', () => { const fn = always(F()) expect(fn()).toBeFalse() expect(fn()).toBeFalse() }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#always) ### and ```typescript and<T, U>(x: T, y: U): T | U ``` Logical AND ```javascript R.and(true, true); // => true R.and(false, true); // => false R.and(true, 'foo'); // => 'foo' ``` <a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.and(true%2C%20true)%3B%20%2F%2F%20%3D%3E%20true%0AR.and(false%2C%20true)%3B%20%2F%2F%20%3D%3E%20false%0AR.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> <details> <summary><strong>R.and</strong> source</summary> ```javascript export function and(a, b){ if (arguments.length === 1) return _b => and(a, _b) return a && b } ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { and } from './and' test('happy', () => { expect(and(1, 'foo')).toBe('foo') expect(and(true, true)).toBeTrue() expect(and(true)(true)).toBeTrue() expect(and(true, false)).toBeFalse() expect(and(false, true)).toBeFalse() expect(and(false, false)).toBeFalse() }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#and) ### any ```typescript any<T>(predicate: (x: T) => boolean, list: readonly 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%20result%20%3D%20const%20list%20%3D%20%5B1%2C%202%2C%203%5D%0Aconst%20predicate%20%3D%20x%20%3D%3E%20x%20*%20x%20%3E%208%0AR.any(fn%2C%20list)%0A%2F%2F%20%3D%3E%20true">Try 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' 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: readonly 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' import { type } from './type' 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' 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: readonly SafePred<T>[]): SafePred<T> ``` It accepts list of `predicates` and returns a function. This function with its `input` will return `true`, if any of `predicates` returns `true` for this `input`. ```javascript const isBig = x => x > 20 const isOdd = x => x % 2 === 1 const input = 11 const fn = R.anyPass( [isBig, isOdd] ) const result = fn(input) // => true ``` <a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20isBig%20%3D%20x%20%3D%3E%20x%20%3E%2020%0Aconst%20isOdd%20%3D%20x%20%3D%3E%20x%20%25%202%20%3D%3D%3D%201%0Aconst%20input%20%3D%2011%0A%0Aconst%20fn%20%3D%20R.anyPass(%0A%20%20%5BisBig%2C%20isOdd%5D%0A)%0A%0Aconst%20result%20%3D%20fn(input)%20%0A%2F%2F%20%3D%3E%20true">Try 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' test('happy', () => { const rules = [ x => typeof x === 'string', x => x > 10 ] const predicate = anyPass(rules) expect(predicate('foo')).toBeTrue() expect(predicate(6)).toBeFalse() }) test('happy', () => { const rules = [ x => typeof x === 'string', x => x > 10 ] expect(anyPass(rules)(11)).toBeTrue() expect(anyPass(rules)(undefined)).toBeFalse() }) const obj = { a : 1, b : 2, } test('when returns true', () => { const conditionArr = [ val => val.a === 1, val => val.a === 2 ] expect(anyPass(conditionArr)(obj)).toBeTrue() }) test('when returns false + curry', () => { const conditionArr = [ val => val.a === 2, val => val.b === 3 ] expect(anyPass(conditionArr)(obj)).toBeFalse() }) test('happy', () => { expect(anyPass([])(3)).toEqual(false) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#anyPass) ### anyTrue ```typescript anyTrue(...input: readonly 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' import { type } from './type' 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' 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: readonly 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' 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' 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) ### append ```typescript append<T>(x: T, list: readonly T[]): readonly T[] ``` It adds element `x` at the end of `list`. ```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 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 = input.slice() clone.push(x) return clone } ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { append } from './append' 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) ### applyDiff ```typescript applyDiff<Output>(rules: readonly 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, obj) 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%20obj)%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 { assocPath } from './assocPath' import { path as pathModule } from './path' const ALLOWED_OPERATIONS = [ 'remove', 'add', 'update' ] export function removeAtPath(path, obj){ const p = typeof path === 'string' ? path.split('.') : 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 return clone = assocPath( path, value, clone ) } if (op === 'remove'){ if (pathModule(path, obj) === undefined) return return removeAtPath(path, clone) } if (op === 'update' && path && value !== undefined){ if (pathModule(path, obj) === undefined) return return clone = assocPath( path, value, clone ) } }) return clone } ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { applyDiff } from './applyDiff' 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, }, ] const result = applyDiff(rules, { a : { b : 1, c : [ 1, 2 ], }, }) expect(result).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, (...args: readonly any[]) => any>>( spec: Spec ): ( ...args: Parameters<ValueOfRecord<Spec>> ) => { readonly [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' // recursively traverse the given spec object to find the highest arity function function __findHighestArity(spec, max = 0){ for (const key in spec){ if (spec.hasOwnProperty(key) === false || key === 'constructor') continue if (typeof spec[ key ] === 'object'){ max = Math.max(max, __findHighestArity(spec[ key ])) } if (typeof spec[ key ] === 'function'){ max = Math.max(max, spec[ key ].length) } } return max } function __filterUndefined(){ const defined = [] let i = 0 const l = arguments.length while (i < l){ if (typeof arguments[ i ] === 'undefined') break defined[ i ] = arguments[ i ] i++ } return defined } function __applySpecWithArity( spec, arity, cache ){ const remaining = arity - cache.length if (remaining === 1) return x => __applySpecWithArity( spec, arity, __filterUndefined(...cache, x) ) if (remaining === 2) return (x, y) => __applySpecWithArity( spec, arity, __filterUndefined( ...cache, x, y ) ) if (remaining === 3) return ( x, y, z ) => __applySpecWithArity( spec, arity, __filterUndefined( ...cache, x, y, z ) ) if (remaining === 4) return ( x, y, z, a ) => __applySpecWithArity( spec, arity, __filterUndefined( ...cache, x, y, z, a ) ) if (remaining > 4) return (...args) => __applySpecWithArity( spec, arity, __filterUndefined(...cache, ...args) ) // handle spec as Array if (_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' import { applySpec } from './applySpec' test('different than Ramda when bad spec', () => { const result = applySpec({ sum : { a : 1 } })(1, 2) const ramdaResult = applySpecRamda({ sum : { a : 1 } })(1, 2) expect(result).toEqual({}) expect(ramdaResult).toEqual({ sum : { a : {} } }) }) test('works with empty spec', () => { expect(applySpec({})()).toEqual({}) expect(applySpec([])(1, 2)).toEqual({}) expect(applySpec(null)(1, 2)).toEqual({}) }) test('works with unary functions', () => { const result = applySpec({ v : inc, u : dec, })(1) const expected = { v : 2, u : 0, } expect(result).toEqual(expected) }) test('works with binary functions', () => { const result = applySpec({ sum : add })(1, 2) expect(result).toEqual({ sum : 3 }) }) test('works with nested specs', () => { const result = applySpec({ unnested : always(0), nested : { sum : add }, })(1, 2) const expected = { unnested : 0, nested : { sum : 3 }, } expect(result).toEqual(expected) }) test('works with arrays of nested specs', () => { const result = applySpec({ unnested : always(0), nested : [ { sum : add } ], })(1, 2) expect(result).toEqual({ unnested : 0, nested : [ { sum : 3 } ], }) }) test('works with arrays of spec objects', () => { const result = applySpec([ { sum : add } ])(1, 2) expect(result).toEqual([ { sum : 3 } ]) }) test('works with arrays of functions', () => { const result = applySpec([ map(prop('a')), map(prop('b')) ])([ { a : 'a1', b : 'b1', }, { a : 'a2', b : 'b2', }, ]) const expected = [ [ 'a1', 'a2' ], [ 'b1', 'b2' ], ] expect(result).toEqual(expected) }) test('works with a spec defining a map key', () => { expect(applySpec({ map : prop('a') })({ a : 1 })).toEqual({ map : 1 }) }) test('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.length).toBe(0) expect(fRamda.length).toBe(5) }) test('returns a curried function', () => { expect(applySpec({ sum : add })(1)(2)).toEqual({ sum : 3 }) }) // Additional tests // ============================================ test('arity', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, } expect(applySpec( spec, 1, 2, 3 )).toEqual({ one : 1, two : 3, three : 6, }) }) test('arity over 5 arguments', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, four : ( x1, x2, x3, x4 ) => x1 + x2 + x3 + x4, five : ( x1, x2, x3, x4, x5 ) => x1 + x2 + x3 + x4 + x5, } expect(applySpec( spec, 1, 2, 3, 4, 5 )).toEqual({ one : 1, two : 3, three : 6, four : 10, five : 15, }) }) test('curried', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, } expect(applySpec(spec)(1)(2)(3)).toEqual({ one : 1, two : 3, three : 6, }) }) test('curried over 5 arguments', () => { const spec = { one : x1 => x1, two : (x1, x2) => x1 + x2, three : ( x1, x2, x3 ) => x1 + x2 + x3, four : ( x1, x2, x3, x4 ) => x1 + x2 + x3 + x4, five : ( x1, x2, x3, x4, x5 ) => x1 + x2 + x3 + x4 + x5, } expect(applySpec(spec)(1)(2)(3)(4)(5)).toEqual({ one : 1, two : 3, three : 6, four : 10, five : 15, }) }) test('undefined property', () => { const spec = { prop : path([ 'property', 'doesnt', 'exist' ]) } expect(applySpec(spec, {})).toEqual({ prop : undefined }) }) test('restructure json object', () => { const spec = { id : path('user.id'), name : path('user.firstname'), profile : path('user.profile'), doesntExist : path('user.profile.doesntExist'), info : { views : compose(inc, prop('views')) }, type : always('playa'), } const data = { user : { id : 1337, firstname : 'john', lastname : 'shaft', profile : 'shaft69', }, views : 42, } expect(applySpec(spec, data)).toEqual({ id : 1337, name : 'john', profile : 'shaft69', doesntExist : undefined, info : { views : 43 }, type : 'playa', }) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#applySpec) ### assoc ```typescript assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & U ``` It makes a shallow clone of `obj` with setting or overriding the property `prop` with `newValue`. > :boom: This copies and flattens prototype properties onto the new object as well. All non-primitive properties are copied by reference. ```javascript R.assoc('c', 3, {a: 1, b: 2}) // => {a: 1, b: 2, c: 3} ``` <a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20result%20%3D%20R.assoc('c'%2C%203%2C%20%7Ba%3A%201%2C%20b%3A%202%7D)%0A%2F%2F%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> <details> <summary><strong>R.assoc</strong> source</summary> ```javascript import { curry } from './curry' function assocFn( prop, newValue, obj ){ return Object.assign( {}, obj, { [ prop ] : newValue } ) } export const assoc = curry(assocFn) ``` </details> <details> <summary><strong>Tests</strong></summary> ```javascript import { assoc } from './assoc' test('adds a key to an empty object', () => { expect(assoc( 'a', 1, {} )).toEqual({ a : 1 }) }) test('adds a key to a non-empty object', () => { expect(assoc( 'b', 2, { a : 1 } )).toEqual({ a : 1, b : 2, }) }) test('adds a key to a non-empty object - curry case 1', () => { expect(assoc('b', 2)({ a : 1 })).toEqual({ a : 1, b : 2, }) }) test('adds a key to a non-empty object - curry case 2', () => { expect(assoc('b')(2, { a : 1 })).toEqual({ a : 1, b : 2, }) }) test('adds a key to a non-empty object - curry case 3', () => { const result = assoc('b')(2)({ a : 1 }) expect(result).toEqual({ a : 1, b : 2, }) }) test('changes an existing key', () => { expect(assoc( 'a', 2, { a : 1 } )).toEqual({ a : 2 }) }) test('undefined is considered an empty object', () => { expect(assoc( 'a', 1, undefined )).toEqual({ a : 1 }) }) test('null is considered an empty object', () => { expect(assoc( 'a', 1, null )).toEqual({ a : 1 }) }) test('value can be null', () => { expect(assoc( 'a', null, null )).toEqual({ a : null }) }) test('value can be undefined', () => { expect(assoc( 'a', undefined, null )).toEqual({ a : undefined }) }) test('assignment is shallow', () => { expect(assoc( 'a', { b : 2 }, { a : { c : 3 } } )).toEqual({ a : { b : 2 } }) }) ``` </details> [![---------------](https://raw.githubusercontent.com/selfrefactor/rambda/master/files/separator.png)](#assoc) ### assocP