Miss any of our Open RFC calls?Watch the recordings here! »

rambda

6.3.1 • Public • Published

Rambda

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

CircleCI codecov dependencies Status Commit activity All contributors Library 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.

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.03 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
  • apply
  • applyTo
  • ascend
  • binary
  • bind
  • call
  • comparator
  • composeK
  • composeP
  • composeWith
  • construct
  • constructN
  • contains
  • countBy
  • descend
  • differenceWith
  • dissocPath
  • empty
  • eqBy
  • forEachObjIndexed
  • gt
  • gte
  • hasIn
  • innerJoin
  • insert
  • insertAll
  • into
  • invert
  • invertObj
  • invoker
  • juxt
  • keysIn
  • lift
  • liftN
  • lt
  • lte
  • mapAccum
  • mapAccumRight
  • mapObjIndexed
  • memoizeWith
  • mergeDeepLeft
  • mergeDeepWith
  • mergeDeepWithKey
  • mergeRight
  • mergeWith
  • mergeWithKey
  • nAry
  • nthArg
  • o
  • objOf
  • otherwise
  • pair
  • partialRight
  • pathSatisfies
  • pickBy
  • pipeK
  • pipeP
  • pipeWith
  • project
  • propSatisfies
  • reduceBy
  • reduceRight
  • reduceWhile
  • reduced
  • remove
  • scan
  • sequence
  • sortWith
  • symmetricDifferenceWith
  • andThen
  • toPairsIn
  • transduce
  • traverse
  • unapply
  • unary
  • uncurryN
  • unfold
  • unionWith
  • uniqBy
  • unnest
  • until
  • useWith
  • valuesIn
  • xprod
  • thunkify
  • default

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

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

  • 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.19) and Ramda(0.27.0).

method Rambda Ramda Lodash
add 96.25% slower 96.24% slower 🚀 Fastest
adjust 🚀 Fastest 5.52% slower 🔳
all 🚀 Fastest 94.95% slower 🔳
allPass 🚀 Fastest 98.95% slower 🔳
any 🚀 Fastest 98.18% slower 6.18% slower
anyPass 🚀 Fastest 99.09% slower 🔳
append 🚀 Fastest 84.09% slower 🔳
applySpec 🚀 Fastest 75.73% slower 🔳
assoc 87.98% slower 57.39% slower 🚀 Fastest
clone 🚀 Fastest 96.03% slower 91.75% slower
compose 🚀 Fastest 96.45% slower 77.83% slower
converge 49.12% slower 🚀 Fastest 🔳
curry 🚀 Fastest 34.9% slower 🔳
curryN 63.32% slower 🚀 Fastest 🔳
defaultTo 🚀 Fastest 50.3% slower 🔳
drop 🚀 Fastest 97.45% slower 🔳
dropLast 🚀 Fastest 97.07% slower 🔳
equals 72.11% slower 79.48% slower 🚀 Fastest
filter 🚀 Fastest 94.74% slower 58.18% slower
find 🚀 Fastest 98.2% slower 88.96% slower
findIndex 🚀 Fastest 97.97% slower 79.39% slower
flatten 6.56% slower 95.38% slower 🚀 Fastest
ifElse 🚀 Fastest 70.97% slower 🔳
includes 🚀 Fastest 71.7% slower 🔳
indexOf 🚀 Fastest 84.08% slower 7.86% slower
init 94.42% slower 97.55% slower 🚀 Fastest
is 🚀 Fastest 11.72% slower 🔳
isEmpty 51.68% slower 93.82% slower 🚀 Fastest
last 🚀 Fastest 99.64% slower 1.05% slower
lastIndexOf 🚀 Fastest 42.38% slower 🔳
map 🚀 Fastest 69.63% slower 4.68% slower
match 🚀 Fastest 46.75% slower 🔳
merge 63.55% slower 🚀 Fastest 55.25% slower
none 🚀 Fastest 98.22% slower 🔳
omit 🚀 Fastest 70.66% slower 97.56% slower
over 🚀 Fastest 50.77% slower 🔳
path 🚀 Fastest 74.94% slower 5.72% slower
pick 🚀 Fastest 26.29% slower 86.82% slower
prop 🚀 Fastest 89.89% slower 🔳
propEq 🚀 Fastest 95.26% slower 🔳
range 95.17% slower 90.22% slower 🚀 Fastest
reduce 52.76% slower 74.02% slower 🚀 Fastest
repeat 85.91% slower 95.31% slower 🚀 Fastest
replace 0.47% slower 28.13% slower 🚀 Fastest
set 🚀 Fastest 36.26% slower 🔳
sort 🚀 Fastest 63.15% slower 🔳
sortBy 🚀 Fastest 61.57% slower 88.88% slower
split 🚀 Fastest 85.34% slower 33.69% slower
splitEvery 🚀 Fastest 90.18% slower 🔳
take 93.44% slower 98.04% slower 🚀 Fastest
takeLast 92.61% slower 98.83% slower 🚀 Fastest
test 🚀 Fastest 94.42% slower 🔳
type 18.91% slower 🚀 Fastest 🔳
uniq 98.98% slower 96.58% slower 🚀 Fastest
update 🚀 Fastest 38.88% slower 🔳
view 🚀 Fastest 82.21% slower 🔳

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

➤ Used by

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

API

add

 
add(anumber, bnumber)number

It adds a and b.

💥 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

All Typescript definitions
add(anumber, bnumber)number;
add(anumber): (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'
 
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)
})
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
  })
})

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

adjust

 
adjust<T>(indexnumber, replaceFn: (x: T) => T, listT[])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

All Typescript definitions
adjust<T>(indexnumber, replaceFn: (x: T) => T, listT[])T[];
adjust<T>(indexnumber, replaceFn: (x: T) => T): (list: T[]) => T[];
R.adjust source
import { curry } from './curry'
 
function adjustFn(
  index, replaceFn, list
){
  const actualIndex = index < 0 ? list.length + index : index
  if (index >= list.length || actualIndex < 0) return list
 
  const clone = list.slice()
  clone[ actualIndex ] = replaceFn(clone[ actualIndex ])
 
  return clone
}
 
export const adjust = curry(adjustFn)
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)
})
1 failed Ramda.adjust specs

💥 Reason for the failure: Ramda method accepts an array-like object

var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
 
describe('adjust', function() {
  it('accepts an array-like object', function() {
    function args() {
      return arguments;
    }
    eq(R.adjust(2, R.add(1), args(0, 1, 2, 3)), [0, 1, 3, 3]);
  });
});

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

all

 
all<T>(predicate: (x: T) => boolean, listT[])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

All Typescript definitions
all<T>(predicate: (x: T) => boolean, listT[])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
  })
})

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

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

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()
})
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
  })
})
1 failed Ramda.allPass specs

💥 Reason for the failure: Ramda method returns a curried function whose arity matches that of the highest-arity predicate

var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
 
describe('allPass', function() {
  var odd = function(n) { return n % 2 !== 0; };
  var lt20 = function(n) { return n < 20; };
  var gt5 = function(n) { return n > 5; };
  var plusEq = function(w, x, y, z) { return w + x === y + z; };
  it('returns a curried function whose arity matches that of the highest-arity predicate', function() {
    eq(R.allPass([odd, gt5, plusEq]).length, 4);
    eq(R.allPass([odd, gt5, plusEq])(9, 9, 9, 9), true);
    eq(R.allPass([odd, gt5, plusEq])(9)(9)(9)(9), true);
  });
});

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

always

 
always<T>(xT): () => T

It returns function that always returns x.

const fn = R.always(7)
 
console.log(fn())// => 7

Try this R.always example in Rambda REPL

All Typescript definitions
always<T>(xT): () => 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 () => string
    const result = fn()
    result // $ExpectType string
  })
})

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

and

 
and<T, U>(xT, yU)T | U

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

All Typescript definitions
and<T, U>(xT, yU)T | U;
and<T>(xT): <U>(y: U) => T | U;
R.and source
export function and(a, b){
  if (arguments.length === 1) return _b => and(a, _b)
 
  return a && b
}
Tests
import { and } from './and'
 
test('happy', () => {
  expect(and(1, 'foo')).toBe('foo')
  expect(and(true, true)).toBeTrue()
  expect(and(true)(true)).toBeTrue()
  expect(and(true, false)).toBeFalse()
  expect(and(false, true)).toBeFalse()
  expect(and(false, false)).toBeFalse()
})
Typescript test
import {and} from 'rambda'
 
describe('R.and', () => {
  it('happy', () => {
    const result = and(true, false)
    result // $ExpectType boolean
  })
  it('curried', () => {
    const result = and('foo')(1)
    result // $ExpectType string | 1
  })
})

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

any

 
any<T>(predicate: (x: T) => boolean, listreadonly 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

All Typescript definitions
any<T>(predicate: (x: T) => boolean, listreadonly T[])boolean;
any<T>(predicate: (x: T) => boolean): (list: readonly 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
  })
})

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

anyPass

 
anyPass<T>(predicatesSafePred<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

All Typescript definitions
anyPass<T>(predicatesSafePred<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('happy', () => {
  expect(anyPass([])(3)).toEqual(false)
})
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
  })
})
1 failed Ramda.anyPass specs

💥 Reason for the failure: Ramda method returns a curried function whose arity matches that of the highest-arity predicate

var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
 
describe('anyPass', function() {
  var odd = function(n) { return n % 2 !== 0; };
  var gt20 = function(n) { return n > 20; };
  var lt5 = function(n) { return n < 5; };
  var plusEq = function(w, x, y, z) { return w + x === y + z; };
  it('returns a curried function whose arity matches that of the highest-arity predicate', function() {
    eq(R.anyPass([odd, lt5, plusEq]).length, 4);
    eq(R.anyPass([odd, lt5, plusEq])(6, 7, 8, 9), false);
    eq(R.anyPass([odd, lt5, plusEq])(6)(7)(8)(9), false);
  });
});

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

append

 
append<T>(xT, listreadonly 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

All Typescript definitions
append<T>(xT, listreadonly T[])T[];
append<T>(xT): <T>(list: readonly T[]) => T[];
R.append source
export function append(x, input){
  if (arguments.length === 1) return _input => append(x, _input)
 
  if (typeof input === 'string') return input.split('').concat(x)
 
  const clone = input.slice()
  clone.push(x)
 
  return clone
}
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[]
  })
})

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

applySpec

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

💥 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

All Typescript definitions
applySpec<Spec extends Record<string, (...args: readonly any[]) => any>>(
  spec: Spec
): (
  ...argsParameters<ValueOfRecord<Spec>>
) => { [Key in keyof Spec]ReturnType<Spec[Key]> };
applySpec<T>(specany): (...args: readonly 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 (< 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
  })
})

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

assoc

 
assoc<T, U, K extends string>(propK, valT, objU)Record<K, T> & U

It makes a shallow clone of obj with setting or overriding the property prop with newValue.

💥 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

All Typescript definitions
assoc<T, U, K extends string>(propK, valT, objU)Record<K, T> & U;
assoc<T, K extends string>(propK, valT): <U>(obj: U) => Record<K, T> & U;
assoc<K extends string>(propK)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
  })
})

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

assocPath

 
assocPath<Output>(pathPath, newValueany, objobject)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

All Typescript definitions
assocPath<Output>(pathPath, newValueany, objobject)Output;
assocPath<Output>(pathPath, newValueany): (obj: object) => Output;
assocPath<Output>(pathPath)FunctionToolbelt.Curry<(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'
 
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 = input.slice()
    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
  })
})

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

both

 
both(pred1Pred, pred2Pred)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

All Typescript definitions
both(pred1Pred, pred2Pred)Pred;
both<T>(pred1Predicate<T>, pred2Predicate<T>)Predicate<T>;
both<T>(pred1Predicate<T>): (pred2: Predicate<T>) => Predicate<T>;
both(pred1Pred): (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
  })
})
1 failed Ramda.both specs

💥 Reason for the failure: Ramda library supports fantasy-land

var S = require('sanctuary');
 
var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
describe('both', function() {
  it('accepts fantasy-land applicative functors', function() {
    var Just = S.Just;
    var Nothing = S.Nothing;
    eq(R.both(Just(true), Just(true)), Just(true));
    eq(R.both(Just(true), Just(false)), Just(false));
    eq(R.both(Just(true), Nothing()), Nothing());
    eq(R.both(Nothing(), Just(false)), Nothing());
    eq(R.both(Nothing(), Nothing()), Nothing());
  });
});

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

chain

 
chain<T, U>(fn: (n: T) => U[], listreadonly 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

All Typescript definitions
chain<T, U>(fn: (n: T) => U[], listreadonly T[])U[];
chain<T, U>(fn: (n: T) => U[]): (list: readonly T[]) => U[];
chain<X0, X1, R>(fn: (x0: X0, x1: X1) => R, fn1: (x1: X1) => X0): (x1: X1) => R;
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'
 
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 ] ])
})
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 resultCurried = chain(fn)(list)
    resultCurried // $ExpectType string[]
  })
})

5 failed Ramda.chain specs

💥 Reason for the failure: Ramda method passes to chain property if available | Ramda library supports fantasy-land

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

clamp

 
clamp(minnumber, maxnumber, inputnumber)number

Restrict a number input to be withing 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.

R.clamp(0, 10, 5) //=> 5
R.clamp(0, 10, -1) //=> 0
R.clamp(0, 10, 11) //=> 10

Try this R.clamp example in Rambda REPL

All Typescript definitions
clamp(minnumber, maxnumber, inputnumber)number;
clamp(minnumber, maxnumber): (input: number) => number;
R.clamp source
import { curry } from './curry'
 
function clampFn(
  min, max, input
){
  if (min > max){
    throw new Error('min must not be greater than max in clamp(min, max, value)')
  }
  if (input >= min && input <= max) return input
 
  if (input > max) return max
  if (input < min) return min
}
 
export const clamp = curry(clampFn)
Tests
import { clamp } from './clamp'
 
test('when min is greater than max', () => {
  expect(() => clamp(
    -5, -10, 5
  )).toThrowWithMessage(Error,
    'min must not be greater than max in clamp(min, max, value)')
})
 
test('rambda specs', () => {
  expect(clamp(
    1, 10, 0
  )).toEqual(1)
  expect(clamp(
    3, 12, 1
  )).toEqual(3)
  expect(clamp(
    -15, 3, -100
  )).toEqual(-15)
  expect(clamp(
    1, 10, 20
  )).toEqual(10)
  expect(clamp(
    3, 12, 23
  )).toEqual(12)
  expect(clamp(
    -15, 3, 16
  )).toEqual(3)
  expect(clamp(
    1, 10, 4
  )).toEqual(4)
  expect(clamp(
    3, 12, 6
  )).toEqual(6)
  expect(clamp(
    -15, 3, 0
  )).toEqual(0)
})
Typescript test
import {clamp} from 'rambda'
 
describe('R.clamp', () => {
  it('happy', () => {
    const result = clamp(1, 10, 20)
    result // $ExpectType number
  })
})

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

clone

 
clone<T>(inputT)T

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 ]

Try this R.clone example in Rambda REPL

All Typescript definitions
clone<T>(inputT)T;
clone<T>(inputreadonly T[])T[];
R.clone source
import { _isArray } from './_internals/_isArray'
 
export function clone(input){
  const out = _isArray(input) ? Array(input.length) : {}
  if (input && input.getTime) return new Date(input.getTime())
 
  for (const key in input){
    const v = input[ key ]
    out[ key ] =
      typeof v === 'object' && v !== null ?
        v.getTime ?
          new Date(v.getTime()) :
          clone(v) :
        v
  }
 
  return out
}
Tests
import assert from 'assert'
 
import { clone } from './clone'
import { equals } from './equals'
 
test('with array', () => {
  const arr = [
    {
      b : 2,
      c : 'foo',
      d : [ 1, 2, 3 ],
    },
    1,
    new Date(),
    null,
  ]
  expect(clone(arr)).toEqual(arr)
})
 
test('with object', () => {
  const obj = {
    a : 1,
    b : 2,
    c : 3,
    d : [ 1, 2, 3 ],
    e : new Date(),
  }
  expect(clone(obj)).toEqual(obj)
})
 
test('with date', () => {
  const date = new Date(
    2014, 10, 14, 23, 59, 59, 999
  )
 
  const cloned = clone(date)
  assert.notStrictEqual(date, cloned)
  expect(cloned).toEqual(new Date(
    2014, 10, 14, 23, 59, 59, 999
  ))
 
  expect(cloned.getDay()).toEqual(5)
})
 
test('with R.equals', () => {
  const objects = [ { a : 1 }, { b : 2 } ]
 
  const objectsClone = clone(objects)
 
  const result = [
    equals(objects, objectsClone),
    equals(objects[ 0 ], objectsClone[ 0 ]),
  ]
  expect(result).toEqual([ true, true ])
})
Typescript test
import {clone} from 'rambda'
 
describe('R.clone', () => {
  it('happy', () => {
    const obj = {a: 1, b: 2}
    const result = clone(obj)
    result // $ExpectType { a: number; b: number; }
  })
})
9 failed Ramda.clone specs

💥 Reason for the failure: Rambda method work only with objects and arrays

var assert = require('assert');
 
var R = require('../../../../dist/rambda.js');
var eq = require('./shared/eq');
describe('deep clone integers, strings and booleans', function() {
  it('clones integers', function() {
    eq(R.clone(-4), -4);
    eq(R.clone(9007199254740991), 9007199254740991);
  });
  it('clones floats', function() {
    eq(R.clone(-