rambdax
Version:
Extended version of Rambda - a lightweight, faster alternative to Ramda
2,158 lines (1,628 loc) • 495 kB
Markdown
# Rambdax
Extended version of Rambda(utility library) - [Documentation](https://selfrefactor.github.io/rambdax/#/)
`Rambda` is smaller and faster alternative to the popular functional programming library **Ramda**. - [Documentation](https://selfrefactor.github.io/rambda/#/)
[](https://circleci.com/gh/selfrefactor/rambda/tree/master)
[](https://codecov.io/gh/selfrefactor/rambda)
[](https://david-dm.org/selfrefactor/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.
[](#-differences-between-rambda-and-rambdax)
## ❯ Example use
```javascript
import { composeAsync, filter, delay, mapAsync } from 'rambdax'
const result = await composeAsync(
mapAsync(async x => {
await delay(100)
return x + 1
}),
filter(x => x > 1)
)([1, 2, 3])
// => [3, 4]
```
You can test this example in <a href="https://rambda.now.sh?const%20result%20%3D%20await%20R.composeAsync(%0A%20%20R.mapAsync(async%20x%20%3D%3E%20%7B%0A%20%20%20%20await%20R.delay(100)%0A%20%20%20%20return%20x%20%2B%201%0A%20%20%7D)%2C%0A%20%20R.filter(x%20%3D%3E%20x%20%3E%201)%0A)(%5B1%2C%202%2C%203%5D)%0A%2F%2F%20%3D%3E%20%5B3%2C%204%5D">Rambda's REPL</a>
* [Differences between Rambda and Ramda](#differences-between-rambda-and-ramda)
* [API](#api)
* [Changelog](#-changelog)
[](#-example-use)
## ❯ Rambdax's advantages
### 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.
[](#-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>
[](#-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'
```
[](#-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.
[](#-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>
[](#-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)
[](#-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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#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>
[](#assoc)
### assocP