rambdax

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

Usage no npm install needed!

<script type="module">
  import rambdax from 'https://cdn.skypack.dev/rambdax';
</script>

README

Rambdax

Extended version of Rambda(utility library) - Documentation

Rambda is smaller and faster alternative to the popular functional programming library Ramda. - Documentation

CircleCI codecov Library size

❯ Differences between Rambda and Rambdax

Rambdax passthrough all Rambda methods and introduce some new functions.

The idea of Rambdax is to extend Rambda without worring for Ramda compatibility.

---------------

❯ Example use

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 Rambda's REPL

---------------

❯ Rambdax's advantages

Typescript included

Typescript definitions are included in the library, in comparison to Ramda, where you need to additionally install @types/ramda.

Still, you need to be aware that functional programming features in Typescript are in development, which means that using R.compose/R.pipe can be problematic.

Important - Rambdax version 8.0.0(or higher) requires Typescript version 4.2.2(or higher).

Dot notation for R.path, R.paths, R.assocPath and R.lensPath

Standard usage of R.path is R.path(['a', 'b'], {a: {b: 1} }).

In Rambda you have the choice to use dot notation(which is arguably more readable):

R.path('a.b', {a: {b: 1} })

Comma notation for R.pick and R.omit

Similar to dot notation, but the separator is comma(,) instead of dot(.).

R.pick('a,b', {a: 1 , b: 2, c: 3} })
// No space allowed between properties

Extendable with Ramda community projects

Rambdax implements some methods from Ramda community projects, such as R.lensSatisfies, R.lensEq and R.viewOr.

Alternative TS definitions

Alternative TS definitions are available as rambdax/immutable. These are Rambdax definitions linted with ESLint functional/prefer-readonly-type plugin.

---------------

❯ Missing Ramda methods

Click to see the full list of 85 Ramda methods not implemented in Rambda
  • __
  • addIndex
  • ap
  • aperture
  • applyTo
  • ascend
  • binary
  • 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
  • memoizeWith
  • mergeDeepLeft
  • mergeDeepWith
  • mergeDeepWithKey
  • mergeRight
  • mergeWith
  • mergeWithKey
  • nAry
  • nthArg
  • o
  • otherwise
  • pair
  • partialRight
  • pathSatisfies
  • pickBy
  • pipeK
  • pipeP
  • pipeWith
  • project
  • propSatisfies
  • reduceBy
  • reduceRight
  • reduceWhile
  • reduced
  • remove
  • scan
  • sequence
  • sortWith
  • symmetricDifferenceWith
  • andThen
  • toPairsIn
  • transduce
  • traverse
  • unary
  • uncurryN
  • unfold
  • unionWith
  • uniqBy
  • unnest
  • until
  • useWith
  • valuesIn
  • xprod
  • thunkify
  • default

---------------

❯ 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'

---------------

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.

---------------

❯ Benchmarks

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.27.1).

method Rambda Ramda Lodash
add 🚀 Fastest 21.52% slower 82.15% slower
adjust 8.48% slower 🚀 Fastest 🔳
all 🚀 Fastest 1.81% slower 🔳
allPass 🚀 Fastest 91.09% 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 🚀 Fastest 32.45% slower 13.68% slower
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 95.26% slower 10.27% slower
ifElse 🚀 Fastest 58.56% slower 🔳
includes 🚀 Fastest 84.63% slower 🔳
indexOf 🚀 Fastest 76.63% 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 0.87% slower 🚀 Fastest 🔳
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 90.24% slower 🔳
uniqWith 25.38% slower 🚀 Fastest 🔳
uniqWith 14.23% slower 🚀 Fastest 🔳
update 🚀 Fastest 52.35% slower 🔳
view 🚀 Fastest 76.15% slower 🔳

---------------

❯ Used by

---------------

API

add


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.

R.add(2, 3) // =>  5

Try this R.add example in Rambda REPL

R.add source
export function add(a, b) {
  if (arguments.length === 1) return _b => add(a, _b)

  return Number(a) + Number(b)
}
Tests
import {add} from './add'
import {add as addRamda} from 'ramda'
import {compareCombinations} from './_internals/testUtils'

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)
})

const possibleInputs = [
  /foo/,
  'foo',
  true,
  3,
  NaN,
  4,
  [],
  Promise.resolve(1),
]

describe('brute force', () => {
  compareCombinations({
    fn: add,
    fnRamda: addRamda,
    firstInput: possibleInputs,
    secondInput: possibleInputs,
    callback: errorsCounters => {
      expect(errorsCounters).toMatchInlineSnapshot(`
        Object {
          "ERRORS_MESSAGE_MISMATCH": 0,
          "ERRORS_TYPE_MISMATCH": 0,
          "RESULTS_MISMATCH": 0,
          "SHOULD_NOT_THROW": 0,
          "SHOULD_THROW": 0,
          "TOTAL_TESTS": 64,
        }
      `)
    },
  })
})

---------------

adjust


adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[]

It replaces index in array list with the result of replaceFn(list[i]).

R.adjust(
  0,
  a => a + 1,
  [0, 100]
) // => [1, 100]

Try this R.adjust example in Rambda REPL

R.adjust source
import {curry} from './curry'
import {cloneList} from './_internals/cloneList'

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)
Tests
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)
})

---------------

all


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.

const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > -1

const result = R.all(predicate, list)
// => true

Try this R.all example in Rambda REPL

R.all source
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
}
Tests
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()
})

---------------

allFalse


allFalse(...inputs: any[]): boolean

It returns true if all inputs arguments are falsy(empty objects and empty arrays are considered falsy).

Functions are valid inputs, but these functions cannot have their own arguments.

This method is very similar to R.anyFalse, R.anyTrue and R.allTrue

R.allFalse(0, null, [], {}, '', () => false)
// => true

Try this R.allFalse example in Rambda REPL

R.allFalse source
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
}
Tests
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)

---------------

allPass


allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean

It returns true, if all functions of predicates return true, when input is their argument.

const input = {
  a : 1,
  b : 2,
}
const predicates = [
  x => x.a === 1,
  x => x.b === 2,
]
const result = R.allPass(predicates)(input) // => true

Try this R.allPass example in Rambda REPL

R.allPass source
export function allPass(predicates) {
  return (...input) => {
    let counter = 0
    while (counter < predicates.length) {
      if (!predicates[counter](...input)) {
        return false
      }
      counter++
    }

    return true
  }
}
Tests
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()
})

test('works with multiple inputs', () => {
  var fn = function (w, x, y, z) {
    return w + x === y + z
  }
  expect(allPass([fn])(3, 3, 3, 3)).toBeTrue()
})

---------------

allTrue


allTrue(...input: any[]): boolean

It returns true if all inputs arguments are truthy(empty objects and empty arrays are considered falsy).

R.allTrue(1, true, {a: 1}, [1], 'foo', () => true)
// => true

Try this R.allTrue example in Rambda REPL

R.allTrue source
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
}
Tests
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()
})

---------------

allType


allType(targetType: RambdaTypes): (...input: any[]) => boolean

It returns a function which will return true if all of its inputs arguments belong to targetType.

:boom: targetType is one of the possible returns of R.type

const targetType = 'String'

const result = R.allType(
  targetType
)('foo', 'bar', 'baz')
// => true

Try this R.allType example in Rambda REPL

R.allType source
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
  }
}
Tests
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()
})

---------------

always


always<T>(x: T): (...args: unknown[]) => T

It returns function that always returns x.

const fn = R.always(7)

const result = fn()
// => 7

Try this R.always example in Rambda REPL

R.always source
export function always(x) {
  return () => x
}
Tests
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()
})

---------------

and

Logical AND

R.and(true, true); // => true
R.and(false, true); // => false
R.and(true, 'foo'); // => 'foo'

Try this R.and example in Rambda REPL

---------------

any


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.

const list = [1, 2, 3]
const predicate = x => x * x > 8
R.any(fn, list)
// => true

Try this R.any example in Rambda REPL

R.any source
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
}
Tests
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()
})

---------------

anyFalse


anyFalse(...input: any[]): boolean

It returns true if any of inputs is falsy(empty objects and empty arrays are considered falsy).

R.anyFalse(1, {a: 1}, [1], () => false)
// => true

Try this R.anyFalse example in Rambda REPL

R.anyFalse source
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
}
Tests
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()
})

---------------

anyPass


anyPass<T>(predicates: 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.

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

Try this R.anyPass example in Rambda REPL

R.anyPass source
export function anyPass(predicates) {
  return (...input) => {
    let counter = 0
    while (counter < predicates.length) {
      if (predicates[counter](...input)) {
        return true
      }
      counter++
    }

    return false
  }
}
Tests
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('with empty predicates list', () => {
  expect(anyPass([])(3)).toEqual(false)
})

test('works with multiple inputs', () => {
  var 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()
})

---------------

anyTrue


anyTrue(...input: any[]): boolean

It returns true if any of inputs arguments are truthy(empty objects and empty arrays are considered falsy).

R.anyTrue(0, null, [], {}, '', () => true)
// => true

Try this R.anyTrue example in Rambda REPL

R.anyTrue source
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
}
Tests
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()
})

---------------

anyType


anyType(targetType: RambdaTypes): (...input: any[]) => boolean

It returns a function which will return true if at least one of its inputs arguments belongs to targetType.

targetType is one of the possible returns of R.type

:boom: targetType is one of the possible returns of R.type

const targetType = 'String'

const result = R.anyType(
  targetType
)(1, {}, 'foo')
// => true

Try this R.anyType example in Rambda REPL

R.anyType source
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
  }
}
Tests
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()
})

---------------

append


append<T>(x: T, list: T[]): T[]

It adds element x at the end of list.

const x = 'foo'

const result = R.append(x, ['bar', 'baz'])
// => ['bar', 'baz', 'foo']

Try this R.append example in Rambda REPL

R.append source
import {cloneList} from './_internals/cloneList'

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
}
Tests
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'])
})

---------------

apply


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.

const result = R.apply(Math.max, [42, -Infinity, 1337])
// => 1337

Try this R.apply example in Rambda REPL

R.apply source
export function apply(fn, args) {
  if (arguments.length === 1) {
    return _args => apply(fn, _args)
  }

  return fn.apply(this, args)
}
Tests
import {apply} from './apply'
import {bind} from './bind'
import {identity} from './identity'

test('happy', () => {
  expect(apply(identity, [1, 2, 3])).toEqual(1)
})

test('applies function to argument list', function () {
  expect(apply(Math.max, [1, 2, 3, -99, 42, 6, 7])).toEqual(42)
})

test('provides no way to specify context', function () {
  const obj = {
    method: function () {
      return this === obj
    },
  }
  expect(apply(obj.method, [])).toEqual(false)
  expect(apply(bind(obj.method, obj), [])).toEqual(true)
})

---------------

applyDiff


applyDiff<Output>(rules: ApplyDiffRule[], obj: object): Output

It changes paths in an object according to a list of operations. Valid operations are add, update and delete. Its use-case is while writing tests and you need to change the test data.

Note, that you cannot use update operation, if the object path is missing in the input object. Also, you cannot use add operation, if the object path has a value.

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`

Try this R.applyDiff example in Rambda REPL

R.applyDiff source
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
}
Tests
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,
    },
  })
})

---------------

applySpec


applySpec<Spec extends Record<string, (...args: any[]) => any>>(
  spec: Spec
): (
  ...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }

:boom: The currying in this function works best with functions with 4 arguments or less. (arity of 4)

const fn = R.applySpec({
  sum: R.add,
  nested: { mul: R.multiply }
})
const result = fn(2, 4) 
// => { sum: 6, nested: { mul: 8 } }

Try this R.applySpec example in Rambda REPL

R.applySpec source
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
}
Tests
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',
  })
})

---------------

assoc


assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & Omit<U, K>

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.

R.assoc('c', 3, {a: 1, b: 2})
// => {a: 1, b: 2, c: 3}

Try this R.assoc example in Rambda REPL

R.assoc source
import {curry} from './curry'

function assocFn(prop, newValue, obj) {
  return Object.assign({}, obj, {[prop]: newValue})
}

export const assoc = curry(assocFn)
Tests
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}})
})

---------------

assocPath


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.

const path = 'b.c'
const newValue = 2
const obj = { a: 1 }

R.assocPath(path, newValue, obj)
// => { a : 1, b : { c : 2 }}

Try this R.assocPath example in Rambda REPL

R.assocPath source
import {_isArray} from './_internals/_isArray'
import {_isInteger} from './_internals/_isInteger'
import {assoc} from './assoc'
import {curry} from './curry'
import {cloneList} from './_internals/cloneList'

function assocPathFn(path, newValue, input) {
  const pathArrValue =
    typeof path === 'string'
      ? path.split('.').map(x => (_isInteger(Number(x)) ? Number(x) : x))
      : 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
      ? _isInteger(pathArrValue[1])
        ? []
        : {}
      : input[index]

    newValue = assocPathFn(
      Array.prototype.slice.call(pathArrValue, 1),
      newValue,
      nextinput
    )
  }

  if (_isInteger(index) && _isArray(input)) {
    const arr = cloneList(input)
    arr[index] = newValue

    return arr
  }

  return assoc(index, newValue, input)
}

export const assocPath = curry(assocPathFn)
Tests
import {assocPath} from './assocPath'

test('string can be used as path input', () => {
  const testObj = {
    a: [{b: 1}, {b: 2}],
    d: 3,
  }
  const result = assocPath('a.0.b', 10, testObj)
  const expected = {
    a: [{b: 10}, {b: 2}],
    d: 3,
  }
  expect(result).toEqual(expected)
})

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})).toEqual({a: 2})
})

test('undefined is considered an empty object', () => {
  expect(assocPath('a', 1, undefined)).toEqual({a: 1})
})

test('null is considered an empty object', () => {
  expect(assocPath('a', 1, null)).toEqual({a: 1})
})

test('value can be null', () => {
  expect(assocPath('a', null, null)).toEqual({a: null})
})

test('value can be undefined', () => {
  expect(assocPath('a', undefined, null)).toEqual({a: undefined})
})

test('assignment is shallow', () => {
  expect(assocPath('a', {b: 2}, {a: {c: 3}})).toEqual({a: {b: 2}})
})

test('empty array as path', () => {
  const result = assocPath([], 3, {
    a: 1,
    b: 2,
  })
  expect(result).toEqual(3)
})

test('happy', () => {
  const expected = {foo: {bar: {baz: 42}}}
  const result = assocPath(['foo', 'bar', 'baz'], 42, {foo: null})
  expect(result).toEqual(expected)
})

---------------

bind


bind<F extends (...args: any[]) => any, T>(fn: F, thisObj: T): (...args: Parameters<F>) => ReturnType<F>

Creates a function that is bound to a context.

:boom: R.bind does not provide the additional argument-binding capabilities of Function.prototype.bind.

const log = R.bind(console.log, console)
const result = R.pipe(
  R.assoc('a', 2), 
  R.tap(log), 
  R.assoc('a', 3)
)({a: 1}); 
// => result - `{a: 3}`
// => console log - `{a: 2}`

Try this R.bind example in Rambda REPL

R.bind source
import {curryN} from './curryN'

export function bind(fn, thisObj) {
  if (arguments.length === 1) {
    return _thisObj => bind(fn, _thisObj)
  }

  return curryN(fn.length, (...args) => fn.apply(thisObj, args))
}
Tests
import {bind} from './bind'

function Foo(x) {
  this.x = x
}
function add(x) {
  return this.x + x
}
function Bar(x, y) {
  this.x = x
  this.y = y
}
Bar.prototype = new Foo()
Bar.prototype.getX = function () {
  return 'prototype getX'
}

test('returns a function', function () {
  expect(typeof bind(add)(Foo)).toEqual('function')
})

test('returns a function bound to the specified context object', function () {
  const f = new Foo(12)
  function isFoo() {
    return this instanceof Foo
  }
  const isFooBound = bind(isFoo, f)
  expect(isFoo()).toEqual(false)
  expect(isFooBound()).toEqual(true)
})

test('works with built-in types', function () {
  const abc = bind(String.prototype.toLowerCase, 'ABCDEFG')
  expect(typeof abc).toEqual('function')
  expect(abc()).toEqual('abcdefg')
})

test('works with user-defined types', function () {
  const f = new Foo(12)
  function getX() {
    return this.x
  }
  const getXFooBound = bind(getX, f)
  expect(getXFooBound()).toEqual(12)
})

test('works with plain objects', function () {
  const pojso = {
    x: 100,
  }
  function incThis() {
    return this.x + 1
  }
  const incPojso = bind(incThis, pojso)
  expect(typeof incPojso).toEqual('function')
  expect(incPojso()).toEqual(101)
})

test('does not interfere with existing object methods', function () {
  const b = new Bar('a', 'b')
  function getX() {
    return this.x
  }
  const getXBarBound = bind(getX, b)
  expect(b.getX()).toEqual('prototype getX')
  expect(getXBarBound()).toEqual('a')
})

test('preserves arity', function () {
  const f0 = function () {
    return 0
  }
  const f1 = function (a) {
    return a
  }
  const f2 = function (a, b) {
    return a + b
  }
  const f3 = function (a, b, c) {
    return a + b + c
  }

  expect(bind(f0, {}).length).toEqual(0)
  expect(bind(f1, {}).length).toEqual(1)
  expect(bind(f2, {}).length).toEqual(2)
  expect(bind(f3, {}).length).toEqual(3)
})

---------------

both


both(pred1: Pred, pred2: Pred): Pred

It returns a function with input argument.

This function will return true, if both firstCondition and secondCondition return true when input is passed as their argument.

const firstCondition = x => x > 10
const secondCondition = x => x < 20
const fn = R.both(secondCondition)

const result = [fn(15), fn(30)]
// => [true, false]

Try this R.both example in Rambda REPL

R.both source
export function both(f, g) {
  if (arguments.length === 1) return _g => both(f, _g)

  return (...input) => f(...input) && g(...input)
}
Tests
import {both} from './both'

const firstFn = val => val > 0
const secondFn = val => val < 10

test('with curry', () => {
  expect(both(firstFn)(secondFn)(17)).toBeFalse()
})

test('without curry', () => {
  expect(both(firstFn, secondFn)(7)).toBeTrue()
})

test('with multiple inputs', () => {
  const between = function (a, b, c) {
    return a < b && b < c
  }
  const total20 = function (a, b, c) {
    return a + b + c === 20
  }
  const fn = both(between, total20)
  expect(fn(5, 7, 8)).toBeTrue()
})

test('skip evaluation of the second expression', () => {
  let effect = 'not evaluated'
  const F = function () {
    return false
  }
  const Z = function () {
    effect = 'Z got evaluated'
  }
  both(F, Z)()

  expect(effect).toBe('not evaluated')
})

---------------

chain


chain<T, U>(fn: (n: T) => U[], list: T[]): U[]

The method is also known as flatMap.

const duplicate = n => [ n, n ]
const list = [ 1, 2, 3 ]

const result = chain(duplicate, list)
// => [ 1, 1, 2, 2, 3, 3 ]

Try this R.chain example in Rambda REPL

R.chain source
export function chain(fn, list) {
  if (arguments.length === 1) {
    return _list => chain(fn, _list)
  }

  return [].concat(...list.map(fn))
}
Tests
import {chain} from './chain'
import {chain as chainRamda} from 'ramda'

const duplicate = n => [n, n]

test('happy', () => {
  const fn = x => [x * 2]
  const list = [1, 2, 3]

  const result = chain(fn, list)

  expect(result).toEqual([2, 4, 6])
})

test('maps then flattens one level', () => {
  expect(chain(duplicate, [1, 2, 3])).toEqual([1, 1, 2, 2, 3, 3])
})

test('maps then flattens one level - curry', () => {
  expect(chain(duplicate)([1, 2, 3])).toEqual([1, 1, 2, 2, 3, 3])
})

test('flattens only one level', () => {
  const nest = n => [[n]]
  expect(chain(nest, [1, 2, 3])).toEqual([[1], [2], [3]])
})

test('can compose', () => {
  function dec(x) {
    return [x - 1]
  }
  function times2(x) {
    return [x * 2]
  }

  var mdouble = chain(times2)
  var mdec = chain(dec)
  expect(mdec(mdouble([10, 20, 30]))).toEqual([19, 39, 59])
})

test('@types/ramda broken test', () => {
  const score = {
    maths: 90,
    physics: 80,
  }

  const calculateTotal = score => {
    const {maths, physics} = score
    return maths + physics
  }

  const assocTotalToScore = (total, score) => ({...score, total})

  const calculateAndAssocTotalToScore = chainRamda(
    assocTotalToScore,
    calculateTotal
  )
  expect(() => calculateAndAssocTotalToScore(score)).toThrow()
})

---------------

clamp

Restrict a number input to be within min and max limits.

If input is bigger than max, then the result is max.

If input is smaller than min, then the result is min.

const result = [
  R.clamp(0, 10, 5), 
  R.clamp(0, 10, -1),
  R.clamp(0, 10, 11)
]
// => [5, 0, 10]

Try this R.clamp example in Rambda REPL

---------------

clone

It creates a deep copy of the input, which may contain (nested) Arrays and Objects, Numbers, Strings, Booleans and Dates.

const objects = [{a: 1}, {b: 2}];
const objectsClone = R.clone(objects);

const result = [
  R.equals(objects, objectsClone),
  R.equals(objects[0], objectsClone[0]),
] // => [ true, true ]

<a title="redirect to Rambda Repl site" href="https://rambda.now.sh?const%20objects%20%3D%20%5B%7Ba%3A%201%7D%2C%20%7Bb%3A%202%7D%5D%3B%0Aconst%20objectsClone%20%3D%20R.clone(objects)%3B%0A%0Aconst%20result%20%3D%20%5B%0A%20%20R.equals(objects%2C