rambda

Lightweight and faster alternative to Ramda with included TS definitions

Usage no npm install needed!

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

README

Rambda

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

CircleCI codecov Commit activity All contributors Library size install size

❯ Example use

import { compose, map, filter } from 'rambda'

const result = compose(
  map(x => x * 2),
  filter(x => x > 2)
)([1, 2, 3, 4])
// => [6, 8]

You can test this example in Rambda's REPL

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

❯ Rambda's advantages

Typescript included

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

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

Important - Rambda version 7.0.0(or higher) requires Typescript version 4.2.2(or higher).

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

Smaller size

The size of a library affects not only the build bundle size but also the dev bundle size and build time. This is important advantage, expecially for big projects.

Tree-shaking

Currently Rambda is more tree-shakable than Ramda - proven in the following repo.

The repo holds two Angular9 applications: one with small example code of Ramda and the other - same code but with Rambda as import library.

The test shows that Rambda bundle size is 2 MB less than its Ramda counterpart.

There is also Webpack/Rollup/Parcel/Esbuild tree-shaking example including several libraries including Ramda, Rambda and Rambdax.

actually tree-shaking is the initial reason for creation of Rambda

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

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

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

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

Comma notation for R.pick and R.omit

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

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

Speed

Rambda is generally more performant than Ramda as the benchmarks can prove that.

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 Rambda to NPM.

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

❯ Missing Ramda methods

Click to see the full list of 90 Ramda methods not implemented in Rambda
  • __
  • addIndex
  • ap
  • aperture
  • applyTo
  • ascend
  • binary
  • call
  • collectBy
  • comparator
  • composeWith
  • construct
  • constructN
  • count
  • 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
  • modify
  • modifyPath
  • nAry
  • partialObject
  • nthArg
  • o
  • on
  • otherwise
  • pair
  • partialRight
  • pathSatisfies
  • pickBy
  • pipeWith
  • project
  • promap
  • propSatisfies
  • reduceBy
  • reduceRight
  • reduceWhile
  • reduced
  • remove
  • scan
  • sequence
  • sortWith
  • splitWhenever
  • symmetricDifferenceWith
  • andThen
  • toPairsIn
  • transduce
  • traverse
  • unary
  • uncurryN
  • unfold
  • unionWith
  • uniqBy
  • unnest
  • until
  • unwind
  • useWith
  • valuesIn
  • whereAny
  • xprod
  • thunkify
  • default

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

❯ Install

  • yarn add rambda

  • For UMD usage either use ./dist/rambda.umd.js or the following CDN link:

https://unpkg.com/rambda@CURRENT_VERSION/dist/rambda.umd.js
  • with deno
import {compose, add} from 'https://raw.githubusercontent.com/selfrefactor/rambda/master/dist/rambda.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.

If you need more Ramda methods in Rambda, you may either submit a PR or check the extended version of Rambda - Rambdax. In case of the former, you may want to consult with Rambda contribution guidelines.

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

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

All Typescript definitions
add(a: number, b: number): number;
add(a: number): (b: number) => number;
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,
        }
      `)
    },
  })
})
Typescript test
import {add} from 'rambda'

describe('R.add', () => {
  it('happy', () => {
    const result = add(4, 1)

    result // $ExpectType number
  })
  it('curried', () => {
    const result = add(4)(1)

    result // $ExpectType number
  })
})
Rambda is fastest. Ramda is 21.52% slower and Lodash is 82.15% slower
const R = require('../../dist/rambda.js')

const add = [
  {
    label: 'Rambda',
    fn: () => {
      R.add(1, 1)
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      Ramda.add(1, 1)
    },
  },
  {
    label: 'Lodash',
    fn: () => {
      _.add(1, 1)
    },
  },
]

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

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

All Typescript definitions
adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[];
adjust<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
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)
})
Rambda is slower than Ramda with 8.48%
const R = require('../../dist/rambda.js')

const list = [0, 1, 2]
const fn = x => x + 1
const index = 1

const adjust = [
  {
    label: 'Rambda',
    fn: () => {
      R.adjust(index, fn, list)
      R.adjust(index, fn)(list)
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      Ramda.adjust(index, fn, list)
      Ramda.adjust(index, fn)(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.

All Typescript definitions
all<T>(predicate: (x: T) => boolean, list: T[]): boolean;
all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
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()
})
Typescript test
import {all} from 'rambda'

describe('all', () => {
  it('happy', () => {
    const result = all(
      x => {
        x // $ExpectType number
        return x > 0
      },
      [1, 2, 3]
    )
    result // $ExpectType boolean
  })
  it('curried needs a type', () => {
    const result = all<number>(x => {
      x // $ExpectType number
      return x > 0
    })([1, 2, 3])
    result // $ExpectType boolean
  })
})
Rambda is faster than Ramda with 1.81%
const R = require('../../dist/rambda.js')

const {
  uniqListOfObjects,
  uniqListOfStrings,
  rangeOfNumbers,
  uniqListOfLists,
} = require('./_utils.js')

const limit = 100

const modes = [
  [uniqListOfObjects(limit), x => Object.keys(x).length > 2],
  [uniqListOfStrings(limit), x => x.length > 0],
  [uniqListOfLists(limit), x => x.length > 0],
  [rangeOfNumbers(limit), x => x > -1],
]

const applyBenchmark = (fn, input) => {
  return fn(input[1], input[0])
}

const tests = [
  {
    label: 'Rambda',
    fn: R.all,
  },
  {
    label: 'Ramda',
    fn: Ramda.all,
  },
]

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

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.

All Typescript definitions
allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
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()
})
Typescript test
import {allPass} from 'rambda'

describe('allPass', () => {
  it('happy', () => {
    const x = allPass<number>([
      y => {
        y // $ExpectType number
        return typeof y === 'number'
      },
      y => {
        return y > 0
      },
    ])(11)

    x // $ExpectType boolean
  })
})
Rambda is faster than Ramda with 91.09%
const R = require('../../dist/rambda.js')

const {random} = require('rambdax')

const limit = 100
const min = 10
const max = 1200
function createListOfFunctions(fn, fnLimit) {
  return Array(fnLimit)
    .fill(null)
    .map(() => fn())
}

const modes = [
  [
    {foo: 1500},
    createListOfFunctions(
      () => x => Number(x.foo) > random(min, max),
      limit
    ),
  ],
  [
    '1500',
    createListOfFunctions(() => x => Number(x) > random(min, max), limit),
  ],
  [
    [1, 2, 1500],
    createListOfFunctions(() => x => x[2] > random(min, max), limit),
  ],
  [1500, createListOfFunctions(() => x => x > random(min, max), limit)],
]

const applyBenchmark = (fn, input) => {
  return fn(input[1])(input[0])
}
const tests = [
  {
    label: 'Rambda',
    fn: R.allPass,
  },
  {
    label: 'Ramda',
    fn: Ramda.allPass,
  },
]

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

always


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

It returns function that always returns x.

All Typescript definitions
always<T>(x: T): (...args: unknown[]) => T;
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()
})
Typescript test
import {always} from 'rambda'

describe('R.always', () => {
  it('happy', () => {
    const fn = always('foo')
    fn // $ExpectType (...args: unknown[]) => string
    const result = fn()
    result // $ExpectType string
  })
})

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

and

Logical AND

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

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.

All Typescript definitions
any<T>(predicate: (x: T) => boolean, list: T[]): boolean;
any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
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()
})
Typescript test
import {any} from 'rambda'

describe('R.any', () => {
  it('happy', () => {
    const result = any(
      x => {
        x // $ExpectType number
        return x > 2
      },
      [1, 2, 3]
    )
    result // $ExpectType boolean
  })

  it('when curried needs a type', () => {
    const result = any<number>(x => {
      x // $ExpectType number
      return x > 2
    })([1, 2, 3])
    result // $ExpectType boolean
  })
})
Rambda is fastest. Ramda is 92.87% slower and Lodash is 45.82% slower
const R = require('../../dist/rambda.js')

const input = [1, 2, 3, 4]
const fn = val => val > 2

const any = [
  {
    label: 'Rambda',
    fn: () => {
      R.any(fn, input)
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      Ramda.any(fn, input)
    },
  },
  {
    label: 'Lodash.some',
    fn: () => {
      _.some(input, fn)
    },
  },
]

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

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.

All Typescript definitions
anyPass<T>(predicates: SafePred<T>[]): SafePred<T>;
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()
})
Typescript test
import {anyPass} from 'rambda'

describe('anyPass', () => {
  it('happy', () => {
    const x = anyPass<number>([
      y => {
        y // $ExpectType number
        return typeof y === 'number'
      },
      y => {
        return y > 0
      },
    ])(11)

    x // $ExpectType boolean
  })
})
Rambda is faster than Ramda with 98.25%
const R = require('../../dist/rambda.js')

const rules = [x => typeof x === 'boolean', x => x > 20, x => x * 7 < 100]

const anyPass = [
  {
    label: 'Rambda',
    fn: () => {
      R.anyPass(rules)(11)
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      Ramda.anyPass(rules)(11)
    },
  },
]

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

append


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

It adds element x at the end of list.

All Typescript definitions
append<T>(x: T, list: T[]): T[];
append<T>(x: T): <T>(list: T[]) => T[];
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'])
})
Typescript test
import {append} from 'rambda'

const list = [1, 2, 3]

describe('R.append', () => {
  it('happy', () => {
    const result = append(4, list)

    result // $ExpectType number[]
  })
  it('curried', () => {
    const result = append(4)(list)

    result // $ExpectType number[]
  })
})
Rambda is faster than Ramda with 2.07%
const R = require('../../dist/rambda.js')

const append = [
  {
    label: 'Rambda',
    fn: () => {
      R.append(0)([1, 2, 3, 4])
      R.append('bar')('foo')
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      Ramda.append(0)([1, 2, 3, 4])
      Ramda.append('bar')('foo')
    },
  },
]

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

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.

All Typescript definitions
apply<T = any>(fn: (...args: any[]) => T, args: any[]): T;
apply<T = any>(fn: (...args: any[]) => T): (args: any[]) => T;
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)
})
Typescript test
import {apply, identity} from 'rambda'

describe('R.apply', () => {
  it('happy', () => {
    const result = apply<number>(identity, [1, 2, 3])

    result // $ExpectType number
  })
  it('curried', () => {
    const fn = apply<number>(identity)
    const result = fn([1, 2, 3])

    result // $ExpectType number
  })
})

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

applySpec


applySpec<Spec extends Record<string, (...args: any[]) => any>>(
  spec: Spec
): (
  ...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
All Typescript definitions
applySpec<Spec extends Record<string, (...args: any[]) => any>>(
  spec: Spec
): (
  ...args: Parameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]: ReturnType<Spec[Key]> };
applySpec<T>(spec: any): (...args: any[]) => T;
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',
  })
})
Typescript test
import {multiply, applySpec, inc, dec, add} from 'rambda'

describe('applySpec', () => {
  it('ramda 1', () => {
    const result = applySpec({
      v: inc,
      u: dec,
    })(1)
    result // $ExpectType { v: number; u: number; }
  })
  it('ramda 1', () => {
    interface Output {
      sum: number
      multiplied: number
    }
    const result = applySpec<Output>({
      sum: add,
      multiplied: multiply,
    })(1, 2)

    result // $ExpectType Output
  })
})
Rambda is faster than Ramda with 80.43%
const R = require('../../dist/rambda.js')

const curryN = [
  {
    label: 'Rambda',
    fn: () => {
      const data = {
        a: {
          b: {c: 1},
          d: 2,
        },
      }
      const spec = {
        c: R.path(['a', 'b', 'c']),
        d: R.path(['a', 'd']),
      }
      R.applySpec(spec, data)
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      const data = {
        a: {
          b: {c: 1},
          d: 2,
        },
      }
      const spec = {
        c: Ramda.path(['a', 'b', 'c']),
        d: Ramda.path(['a', 'd']),
      }
      Ramda.applySpec(spec, data)
    },
  },
]

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

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.

All Typescript definitions
assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & Omit<U, K>;
assoc<T, K extends string>(prop: K, val: T): <U>(obj: U) => Record<K, T> & Omit<U, K>;
assoc<K extends string>(prop: K): AssocPartialOne<K>;
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}})
})
Typescript test
import {assoc} from 'rambda'

const obj = {a: 1}
const newValue = 2
const newProp = 'b'

describe('R.assoc', () => {
  it('happy', () => {
    const result = assoc(newProp, newValue, obj)

    result.a // $ExpectType number
    result.b // $ExpectType number
  })
  it('curried 1', () => {
    const result = assoc(newProp, newValue)(obj)

    result.a // $ExpectType number
    result.b // $ExpectType number
  })
  it('curried 2', () => {
    const result = assoc(newProp)(newValue)(obj)

    result.a // $ExpectType number
    result.b // $ExpectType number
  })
})
Lodash is fastest. Rambda is 72.32% slower and Ramda is 60.08% slower
const R = require('../../dist/rambda.js')

const input = {
  a: 1,
  b: 2,
}
const key = 'c'
const value = 3

const assoc = [
  {
    label: 'Rambda',
    fn: () => {
      R.assoc(key, value, input)
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      Ramda.assoc(key, value, input)
    },
  },
  {
    label: 'Lodash.set',
    fn: () => {
      _.set(input, key, value)
    },
  },
]

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

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.

All Typescript definitions
assocPath<Output>(path: Path, newValue: any, obj: object): Output;
assocPath<Output>(path: Path, newValue: any): (obj: object) => Output;
assocPath<Output>(path: Path): (newValue: any) => (obj: object) => Output;
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)
})
Typescript test
import {assocPath} from 'rambda'

interface Output {
  a: number
  foo: {bar: number}
}

describe('R.assocPath - user must explicitly set type of output', () => {
  it('with array as path input', () => {
    const result = assocPath<Output>(['foo', 'bar'], 2, {a: 1})

    result // $ExpectType Output
  })
  it('with string as path input', () => {
    const result = assocPath<Output>('foo.bar', 2, {a: 1})

    result // $ExpectType Output
  })
})

describe('R.assocPath - curried', () => {
  it('with array as path input', () => {
    const result = assocPath<Output>(['foo', 'bar'], 2)({a: 1})

    result // $ExpectType Output
  })
  it('with string as path input', () => {
    const result = assocPath<Output>('foo.bar', 2)({a: 1})

    result // $ExpectType Output
  })
})

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

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.

All Typescript definitions
bind<F extends (...args: any[]) => any, T>(fn: F, thisObj: T): (...args: Parameters<F>) => ReturnType<F>;
bind<F extends (...args: any[]) => any, T>(fn: F): (thisObj: T) => (...args: Parameters<F>) => ReturnType<F>;
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)
})
Typescript test
import {bind} from 'rambda'

class Foo {}
function isFoo<T = any>(this: T): boolean {
  return this instanceof Foo
}

describe('R.bind', () => {
  it('happy', () => {
    const foo = new Foo()
    const result = bind(isFoo, foo)()

    result // $ExpectType boolean
  })
})

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

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.

All Typescript definitions
both(pred1: Pred, pred2: Pred): Pred;
both<T>(pred1: Predicate<T>, pred2: Predicate<T>): Predicate<T>;
both<T>(pred1: Predicate<T>): (pred2: Predicate<T>) => Predicate<T>;
both(pred1: Pred): (pred2: Pred) => Pred;
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')
})
Typescript test
import {both} from 'rambda'

describe('R.both', () => {
  it('with passed type', () => {
    const fn = both<number>(
      x => x > 1,
      x => x % 2 === 0
    )
    fn // $ExpectType Predicate<number>
    const result = fn(2) // $ExpectType boolean
    result // $ExpectType boolean
  })
  it('with passed type - curried', () => {
    const fn = both<number>(x => x > 1)(x => x % 2 === 0)
    fn // $ExpectType Predicate<number>
    const result = fn(2)
    result // $ExpectType boolean
  })
  it('no type passed', () => {
    const fn = both(
      x => {
        x // $ExpectType any
        return x > 1
      },
      x => {
        x // $ExpectType any
        return x % 2 === 0
      }
    )
    const result = fn(2)
    result // $ExpectType boolean
  })
  it('no type passed - curried', () => {
    const fn = both((x: number) => {
      x // $ExpectType number
      return x > 1
    })((x: number) => {
      x // $ExpectType number
      return x % 2 === 0
    })
    const result = fn(2)
    result // $ExpectType boolean
  })
})

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

chain


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

The method is also known as flatMap.

All Typescript definitions
chain<T, U>(fn: (n: T) => U[], list: T[]): U[];
chain<T, U>(fn: (n: T) => U[]): (list: T[]) => U[];
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()
})
Typescript test
import {chain} from 'rambda'

const list = [1, 2, 3]
const fn = (x: number) => [`${x}`, `${x}`]

describe('R.chain', () => {
  it('without passing type', () => {
    const result = chain(fn, list)
    result // $ExpectType string[]

    const curriedResult = chain(fn)(list)
    curriedResult // $ExpectType string[]
  })
})

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

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.

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

clone

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

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

complement

It returns inverted version of origin function that accept input as argument.

The return value of inverted is the negative boolean value of origin(input).

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

compose

It performs right-to-left function composition.

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

concat

It returns a new string or array, which is the result of merging x and y.

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

cond

It takes list with conditions and returns a new function fn that expects input as argument.

This function will start evaluating the conditions in order to find the first winner(order of conditions matter).

The winner is this condition, which left side returns true when input is its argument. Then the evaluation of the right side of the winner will be the final result.

If no winner is found, then fn returns undefined.

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

converge

Accepts a converging function and a list of branching functions and returns a new function. When invoked, this new function is applied to some arguments, each branching function is applied to those same arguments. The results of each branching function are passed as arguments to the converging function to produce the return value.

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

curry

It expects a function as input and returns its curried version.

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

curryN

It returns a curried equivalent of the provided function, with the specified arity.

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

dec

It decrements a number.

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

defaultTo


defaultTo<T>(defaultValue: T, input: T | null | undefined): T

It returns defaultValue, if all of inputArguments are undefined, null or NaN.

Else, it returns the first truthy inputArguments instance(from left to right).

All Typescript definitions
defaultTo<T>(defaultValue: T, input: T | null | undefined): T;
defaultTo<T>(defaultValue: T): (input: T | null | undefined) => T;
R.defaultTo source
function isFalsy(input) {
  return (
    input === undefined || input === null || Number.isNaN(input) === true
  )
}

export function defaultTo(defaultArgument, input) {
  if (arguments.length === 1) {
    return _input => defaultTo(defaultArgument, _input)
  }

  return isFalsy(input) ? defaultArgument : input
}
Tests
import {defaultTo} from './defaultTo'

test('with undefined', () => {
  expect(defaultTo('foo')(undefined)).toEqual('foo')
})

test('with null', () => {
  expect(defaultTo('foo')(null)).toEqual('foo')
})

test('with NaN', () => {
  expect(defaultTo('foo')(NaN)).toEqual('foo')
})

test('with empty string', () => {
  expect(defaultTo('foo', '')).toEqual('')
})

test('with false', () => {
  expect(defaultTo('foo', false)).toEqual(false)
})

test('when inputArgument passes initial check', () => {
  expect(defaultTo('foo', 'bar')).toEqual('bar')
})
Typescript test
import {defaultTo} from 'rambda'

describe('R.defaultTo with Ramda spec', () => {
  it('happy', () => {
    const result = defaultTo('foo', '')
    result // $ExpectType "" | "foo"
  })
  it('with explicit type', () => {
    const result = defaultTo<string>('foo', null)
    result // $ExpectType string
  })
})
Rambda is faster than Ramda with 48.91%
const R = require('../../dist/rambda.js')

const input = [null, undefined, 5]

const defaultTo = [
  {
    label: 'Rambda',
    fn: () => {
      R.defaultTo(3, input[0])
    },
  },
  {
    label: 'Ramda',
    fn: () => {
      Ramda.defaultTo(3, input[0])
    },
  },
  {
    label: 'Rambda with multiple arguments',
    fn: () => {
      R.defaultTo(3, ...input)
    },
  },
]

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

difference


difference<T>(a: T[], b: T[]): T[]

It returns the uniq set of all elements in the first list a not contained in the second list b.

R.equals is used to determine equality.

All Typescript definitions
difference<T>(a: T[], b: T[]): T[];
difference<T>(a: T[]): (b: T[]) => T[];
R.difference source
import {includes} from './includes'
import {uniq} from './uniq'

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

  return uniq(a).filter(aInstance => !includes(aInstance, b))
}
Tests
import {difference} from './difference'
import {difference as differenceRamda} from 'ramda'

test('difference', () => {
  const a = [1, 2, 3, 4]
  const b = [3, 4, 5, 6]
  expect(difference(a)(b)).toEqual([1, 2])

  expect(difference([], [])).toEqual([])
})

test('difference with objects', () => {
  const a = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]
  const b = [{id: 3}, {id: 4}, {id: 5}, {id: 6}]
  expect(difference(a, b)).toEqual([{id: 1}, {id: 2}])
})

test('no duplicates in first list', () => {
  const M2 = [1, 2, 3, 4, 1, 2, 3, 4]
  const N2 = [3, 3, 4, 4, 5, 5, 6, 6]
  expect(difference(M2, N2)).toEqual([1, 2])
})

test('should use R.equals', () => {
  expect(difference([1], [1]).length).toEqual(0)
  expect(differenceRamda([NaN], [NaN]).length).toEqual(0)
})
Typescript test
import {difference} from 'rambda'

const list1 = [1, 2, 3]
const list2 = [1, 2, 4]

describe('R.difference', () => {
  it('happy', () => {
    const result = difference(list1, list2)

    result // $ExpectType number[]
  })
  it('curried', () => {
    const result = difference(list1)(list2)

    result // $ExpectType number[]
  })
})

</deta