Learn about our RFC process, Open RFC meetings & more.Join in the discussion! »

rambda

5.6.0 • Public • Published

Rambda

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

CircleCI codecov dependencies Status Normal size Gzip 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

  • Tree-shaking

Currently Rambda is more tree-shakable than Ramda

  • Speed

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

  • dot notation for R.path and R.paths

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
  • 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 due to variadic arguments Typescript proposal being still open and unresolved, using R.compose/R.pipe is far from smooth. The issue has been previously discussed but there is no visible solution to it.

  • More generic methods

Ramda has an overwhelming list of methods, as one could get lost putting all these methods in one's head. Rambda has smaller method counts and that could be seen as advantage.

Click to see the full list of 116 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
  • dropLastWhile
  • dropRepeats
  • dropRepeatsWith
  • dropWhile
  • empty
  • eqBy
  • eqProps
  • evolve
  • forEachObjIndexed
  • gt
  • gte
  • hasIn
  • innerJoin
  • insert
  • insertAll
  • into
  • invert
  • invertObj
  • invoker
  • juxt
  • keysIn
  • lift
  • liftN
  • lt
  • lte
  • mapAccum
  • mapAccumRight
  • mapObjIndexed
  • memoizeWith
  • mergeAll
  • mergeDeepLeft
  • mergeDeepRight
  • mergeDeepWith
  • mergeDeepWithKey
  • mergeLeft
  • mergeRight
  • mergeWith
  • mergeWithKey
  • move
  • nAry
  • nthArg
  • o
  • objOf
  • of
  • once
  • or
  • otherwise
  • pair
  • partialRight
  • partition
  • pathEq
  • pathSatisfies
  • pickBy
  • pipeK
  • pipeP
  • pipeWith
  • project
  • propSatisfies
  • props
  • reduceBy
  • reduceRight
  • reduceWhile
  • reduced
  • remove
  • scan
  • sequence
  • sortWith
  • splitAt
  • splitWhen
  • symmetricDifferenceWith
  • takeLastWhile
  • takeWhile
  • andThen
  • toPairsIn
  • transduce
  • traverse
  • tryCatch
  • unapply
  • unary
  • uncurryN
  • unfold
  • union
  • unionWith
  • uniqBy
  • unless
  • unnest
  • until
  • useWith
  • valuesIn
  • where
  • whereEq
  • xprod
  • zipWith
  • thunkify
  • default

Install

  • yarn add rambda

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

https://unpkg.com/rambda@CURRENT_VERSION/dist/rambda.umd.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 path and paths accept dot notation - 'x.y' same as ['x','y']

  • Rambda's pick and omit accept comma notation - 'x,y' same as ['x','y']

  • Rambda's map, reject and forEach can iterate over objects not only arrays.

  • Rambda's map and filter pass array index as second argument when mapping over arrays.

  • Rambda's adjust, all, allPass, any, anyPass, findIndex , findLastIndex and reject are passing index as second argument to the predicate function.

  • Rambda's filter returns empty array with bad input(null or undefined), while Ramda throws.

  • Ramda's includes will throw an error if input is neither string nor array, while Rambda version will return false.

  • Ramda's clamp work for letters, while Rambda's method work only for numbers.

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

method Rambda Ramda Lodash
add 96.29% slower 96.36% slower 🔳
adjust 🚀 Fastest 7.73% slower 🔳
all 🚀 Fastest 94.56% slower 🔳
allPass 🚀 Fastest 98.98% slower 🔳
any 🚀 Fastest 98.17% slower 🔳
anyPass 🚀 Fastest 99.05% slower 🔳
append 🚀 Fastest 82.87% slower 🔳
applySpec 🚀 Fastest 68.62% slower 🔳
assoc 86.8% slower 54.81% slower 🔳
clone 🚀 Fastest 83.29% slower 🔳
compose 🚀 Fastest 96.3% slower 🔳
converge 41.29% slower 🚀 Fastest 🔳
curry 🚀 Fastest 43.32% slower 🔳
curryN 59.09% slower 🚀 Fastest 🔳
defaultTo 🚀 Fastest 58.91% slower 🔳
drop 🚀 Fastest 97.25% slower 🔳
dropLast 🚀 Fastest 96.77% slower 🔳
equals 75.91% slower 78.74% slower 🔳
filter 66.61% slower 86.94% slower 🔳
find 🚀 Fastest 91.46% slower 🔳
findIndex 🚀 Fastest 97.77% slower 🔳
flatten 96.45% slower 95.36% slower 🔳
ifElse 🚀 Fastest 67.27% slower 🔳
includes 🚀 Fastest 68.65% slower 🔳
indexOf 7.68% slower 85.52% slower 🔳
init 93.7% slower 97.03% slower 🔳
is 🚀 Fastest 13.5% slower 🔳
isEmpty 62.67% slower 93.81% slower 🔳
last 0.35% slower 99.63% slower 🔳
lastIndexOf 🚀 Fastest 39.93% slower 🔳
map 36.87% slower 66.32% slower 🔳
match 🚀 Fastest 44.9% slower 🔳
merge 62.07% slower 🚀 Fastest 🔳
none 🚀 Fastest 88.45% slower 🔳
omit 🚀 Fastest 72.59% slower 🔳
over 🚀 Fastest 51.03% slower 🔳
path 🚀 Fastest 75.54% slower 🔳
pick 🚀 Fastest 23.56% slower 🔳
prop 🚀 Fastest 89.38% slower 🔳
propEq 🚀 Fastest 95.03% slower 🔳
range 95.18% slower 90.24% slower 🔳
reduce 53.68% slower 77.06% slower 🔳
repeat 83.23% slower 94.91% slower 🔳
replace 🚀 Fastest 32.01% slower 🔳
set 🚀 Fastest 41.28% slower 🔳
sort 🚀 Fastest 63.24% slower 🔳
sortBy 🚀 Fastest 60.24% slower 🔳
split 🚀 Fastest 84.91% slower 🔳
splitEvery 🚀 Fastest 89.38% slower 🔳
take 92.22% slower 97.8% slower 🔳
takeLast 92.59% slower 98.75% slower 🔳
test 🚀 Fastest 94.01% slower 🔳
type 37.42% slower 🚀 Fastest 🔳
uniq 99.2% slower 96.53% slower 🔳
update 🚀 Fastest 33.61% slower 🔳
view 🚀 Fastest 77.49% slower 🔳

Used by

API

add

add(anumber, bnumber)number

It adds a and b.

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

Try the above 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('add', () => {
  it('number', () => {
    const result = [
      add(4)(1),
      add(4,1)
    ]  
    result[0] // $ExpectType number
    result[1] // $ExpectType number
  })
})

adjust

adjust<T>(indexnumber, replaceFn: (a: T) => T, listReadonlyArray<T>)T[]

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

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

Try the above R.adjust example in Rambda REPL

All Typescript definitions
adjust<T>(indexnumber, replaceFn: (a: T) => T, listReadonlyArray<T>)T[];
adjust<T>(indexnumber, replaceFn: (a: T) => T): (list: ReadonlyArray<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'
 
const expected = [ 0, 11, 2 ]
 
test('without curring', () => {
  expect(adjust(
    1, add(10), [ 0, 1, 2 ]
  )).toEqual(expected)
})
 
test('with curring type 1 1 1', () => {
  expect(adjust(1)(add(10))([ 0, 1, 2 ])).toEqual(expected)
})
 
test('with curring type 1 2', () => {
  expect(adjust(1)(add(10), [ 0, 1, 2 ])).toEqual(expected)
})
 
test('with curring type 2 1', () => {
  expect(adjust(1, add(10))([ 0, 1, 2 ])).toEqual(expected)
})
 
test('with negative index', () => {
  expect(adjust(
    -2, add(10), [ 0, 1, 2 ]
  )).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 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, listReadonlyArray<T>)boolean

It returns true, if all members of array list returns true, when applied as argument to predicate function.

const list = [ 0, 1, 2, 3, 4 ]
const predicate = x => x > -1
 
const result = R.all(predicate, arr)
// => true

Try the above R.all example in Rambda REPL

All Typescript definitions
all<T>(predicate: (x: T) => boolean, listReadonlyArray<T>)boolean;
all<T>(predicate: (x: T) => boolean): (list: ReadonlyArray<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 ], i)) return false
  }
 
  return true
}
Tests
import { all } from './all'
 
const numArr = [ 0, 1, 2, 3, 4 ]
 
test('when true', () => {
  const fn = x => x > -1
 
  expect(all(fn)(numArr)).toBeTrue()
})
 
test('when false', () => {
  const fn = x => x > 2
 
  expect(all(fn, numArr)).toBeFalse()
})
 
test('pass index as second argument', () => {
  const indexes = []
  const fn = (x, i) => {
    indexes.push(i)
 
    return x > 5
  }
  all(fn, [ 10, 12, 14 ])
 
  expect(indexes).toEqual([ 0, 1, 2 ])
})
Typescript test
import {all} from 'rambda'
 
describe('all', () => {
  it('happy', () => {
    const x = all<number>(y => {
      y // $ExpectType number
      return y > 0
    })([1, 2, 3])
    x // $ExpectType boolean
 
    const q = all(y => y > 0, [1, 2, 3]) // $ExpectType boolean
 
    q // $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 the above 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 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 the above 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'
 
test('happy', () => {
  const fn = always(7)
 
  expect(fn()).toEqual(7)
  expect(fn()).toEqual(7)
})

and

and<T extends { and?: ((...a: readonly any[]) => any)

Returns true if both arguments are true. Otherwise, it returns false.

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

Try the above R.and example in Rambda REPL

All Typescript definitions
and<T extends { and?: ((...a: readonly any[]) => any); } | number | boolean | string | null>(fn1: T, val2: any): boolean;
and<T extends { and?: ((...a: readonly any[]) => any); } | number | boolean | string | null>(fn1: T)(val2: any) => boolean;
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(true, true)).toBe(true)
  expect(and(true, false)).toBe(false)
  expect(and(false, true)).toBe(false)
  expect(and(false, false)).toBe(false)
})

any

any<T>(predicate: (x: T, i: number) => boolean, listReadonlyArray<T>)boolean

It returns true, if at least one member of list returns true, when passed to predicate function.

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

Try the above R.any example in Rambda REPL

All Typescript definitions
any<T>(predicate: (x: T, i: number) => boolean, listReadonlyArray<T>)boolean;
any<T>(predicate: (x: T) => boolean, listReadonlyArray<T>)boolean;
any<T>(predicate: (x: T, i: number) => boolean): (list: ReadonlyArray<T>) => boolean;
any<T>(predicate: (x: T) => boolean): (list: ReadonlyArray<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 arr = [ 1, 2 ]
 
test('no curry', () => {
  expect(any(val => val < 0, arr)).toBeFalse()
})
 
test('with curry', () => {
  expect(any(val => val < 2)(arr)).toBeTrue()
})
 
test('passes index to predicate', () => {
  any((x, i) => {
    expect(typeof x).toBe('string')
    expect(typeof i).toBe('number')
  })([ 'foo', 'bar' ])
})
Typescript test
import {any} from 'rambda'
 
describe('any', () => {
  it('1', () => {
    const x = any<number>(
      (y, i) => {
        y // $ExpectType number
        i // $ExpectType number
        return y > 2
      },
      [1, 2, 3]
    )
    x // $ExpectType boolean
  })
  it('2', () => {
    const x = any<number>(
      y => {
        y // $ExpectType number
        return y > 2
      },
      [1, 2, 3]
    )
    x // $ExpectType boolean
  })
 
  it('1 curry', () => {
    const x = any<number>((y, i) => {
      y // $ExpectType number
      i // $ExpectType number
      return y > 2
    })([1, 2, 3])
    x // $ExpectType boolean
  })
  it('2 curry', () => {
    const x = any<number>(y => {
      y // $ExpectType number
      return y > 2
    })([1, 2, 3])
    x // $ExpectType boolean
  })
})

anyPass

anyPass<T>(predicatesReadonlyArray<SafePred<T>>)SafePred<T>

It accepts list of predicates and returns a function. This function with its input will return true, if any of predicates returns true for this input.

const isBig = x => x > 20
const isOdd = x => x % 2 === 1
const input = 11
 
const fn = R.anyPass(
  [isBig, isOdd]
)
 
const result = fn(input) 
// => true

Try the above R.anyPass example in Rambda REPL

All Typescript definitions
anyPass<T>(predicatesReadonlyArray<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('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 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, listOrStringReadonlyArray<T>)T[]

It adds element x at the end of listOrString.

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

Try the above R.append example in Rambda REPL

All Typescript definitions
append<T>(xT, listOrStringReadonlyArray<T>)T[];
append<T>(xT): <T>(listOrString: ReadonlyArray<T>) => T[];
R.append source
export function append(x, listOrString){
  if (arguments.length === 1)
    return _listOrString => append(x, _listOrString)
 
  if (typeof listOrString === 'string') return `${ listOrString }${ x }`
 
  const clone = listOrString.slice()
  clone.push(x)
 
  return clone
}
Tests
import { append } from './append'
import { compose } from './compose.js'
import { flatten } from './flatten.js'
import { map } from './map'
 
test('with strings', () => {
  expect(append('o', 'fo')).toEqual('foo')
})
 
test('with arrays', () => {
  expect(append('tests', [ 'write', 'more' ])).toEqual([
    'write',
    'more',
    'tests',
  ])
})
 
test('append to empty array', () => {
  expect(append('tests', [])).toEqual([ 'tests' ])
})
 
test('happy', () => {
  const result = compose(flatten, map(append(0)))([ [ 1 ], [ 2 ], [ 3 ] ])
  expect(result).toEqual([ 1, 0, 2, 0, 3, 0 ])
})
 
test('should not modify arguments', () => {
  const a = [ 1, 2, 3 ]
  const b = append(4, a)
 
  expect(a).toEqual([ 1, 2, 3 ])
  expect(b).toEqual([ 1, 2, 3, 4 ])
})

applySpec

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

It returns a curried function with the same arity as the longest function in the spec object. Arguments will be applied to the spec methods recursively.

const spec = {
  name: R.path('deeply.nested.firstname')
}
const json = {
  deeply: {
   nested: {
      firstname: 'barry'
    }
  }
}
const result = R.applySpec(spec, json) // => { name: 'barry' }
 
// Second example
const getMetrics = R.applySpec({
  sum: R.add,
  nested: { mul: R.multiply }
});
getMetrics(2, 4); // => { sum: 6, nested: { mul: 8 } }

Try the above 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
// 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 (Array.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' || Array.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.skip('retains the highest arity', () => {
  const f = applySpec({
    f1 : nAry(2, T),
    f2 : nAry(5, T),
  })
  expect(f.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, newValueT, objU)Record<K, T> & U

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

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

Try the above R.assoc example in Rambda REPL

All Typescript definitions
assoc<T, U, K extends string>(propK, newValueT, objU)Record<K, T> & U;
assoc<T, K extends string>(propK, newValueT): <U>(obj: U) => Record<K, T> & U;
assoc<K extends string>(propK): <T, U>(newValue: T, obj: U) => Record<K, T> & U;
R.assoc source
import { curry } from './curry'
 
function assocFn(
  prop, newValue, obj
){
  return Object.assign(
    {}, obj, { [ prop ] : newValue }
  )
}
 
export const assoc = curry(assocFn)
Tests
import { assoc } from './assoc'
 
test('adds a key to an empty object', () => {
  expect(assoc(
    'a', 1, {}
  )).toEqual({ a : 1 })
})
 
test('adds a key to a non-empty object', () => {
  expect(assoc(
    'b', 2, { a : 1 }
  )).toEqual({
    a : 1,
    b : 2,
  })
})
 
test('adds a key to a non-empty object - curry case 1', () => {
  expect(assoc('b', 2)({ a : 1 })).toEqual({
    a : 1,
    b : 2,
  })
})
 
test('adds a key to a non-empty object - curry case 2', () => {
  expect(assoc('b')(2, { a : 1 })).toEqual({
    a : 1,
    b : 2,
  })
})
 
test('adds a key to a non-empty object - curry case 3', () => {
  const result = assoc('b')(2)({ a : 1 })
 
  expect(result).toEqual({
    a : 1,
    b : 2,
  })
})
 
test('changes an existing key', () => {
  expect(assoc(
    'a', 2, { a : 1 }
  )).toEqual({ a : 2 })
})
 
test('undefined is considered an empty object', () => {
  expect(assoc(
    'a', 1, undefined
  )).toEqual({ a : 1 })
})
 
test('null is considered an empty object', () => {
  expect(assoc(
    'a', 1, null
  )).toEqual({ a : 1 })
})
 
test('value can be null', () => {
  expect(assoc(
    'a', null, null
  )).toEqual({ a : null })
})
 
test('value can be undefined', () => {
  expect(assoc(
    'a', undefined, null
  )).toEqual({ a : undefined })
})
 
test('assignment is shallow', () => {
  expect(assoc(
    'a', { b : 2 }, { a : { c : 3 } }
  )).toEqual({ a : { b : 2 } })
})

assocPath

assocPath<T, U>(pathPath, newValueT, objU)U

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 the above R.assocPath example in Rambda REPL

All Typescript definitions
assocPath<T, U>(pathPath, newValueT, objU)U;
assocPath<T, U>(pathPath, newValueT): (obj: U) => U;
assocPath<T, U>(pathPath)FunctionToolbelt.Curry<(a: T, b: U) => U>;
R.assocPath source
import { _isInteger } from './_internals/_isInteger'
import { assoc } from './assoc'
import { curry } from './curry'
 
function assocPathFn(
  list, newValue, input
){
  const pathArrValue = typeof list === 'string' ? list.split('.') : list
  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(parseInt(pathArrValue[ 1 ], 10)) ?
        [] :
        {} :
      input[ index ]
    newValue = assocPathFn(
      Array.prototype.slice.call(pathArrValue, 1),
      newValue,
      nextinput
    )
  }
 
  if (_isInteger(parseInt(index, 10)) && Array.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('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 nested array to a non-empty object - curry case 1', () => {
  expect(assocPath('b.0', 2)({ a : 1 })).toEqual({
    a : 1,
    b : [ 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('happy', () => {
  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)
})

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 the above 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('both', () => {
  it('with passed type', () => {
    const fn = both<number>( // $ExpectType Predicate<number>
      x => {
        return x > 1
      },
      x => {
        return x % 2 === 0
      }
    )
    const result = fn(2) // $ExpectType boolean
    result // $ExpectType boolean
  })
  it('no type passed', () => {
    const fn = both(
      x => {
        x // $ExpectType any
        return x > 1
      },
      x => {
        return x % 2 === 0
      }
    )
    const result = fn(2) // $ExpectType boolean
    result // $ExpectType boolean
  })
})
 
describe('both + curry', () => {
  it('with passed type', () => {
    const fn = both<number>(x => {
      return x > 1
    })(x => {
      return x % 2 === 0
    })
    fn // $ExpectType Predicate<number>
    const result = fn(2) // $ExpectType boolean
    result // $ExpectType boolean
  })
  it('no type passed', () => {
    const fn = both(x => {
      x // $ExpectType unknown
      return (x as number) > 1
    })(x => {
      return (x as number) % 2 === 0
    })
    const result = fn(2) // $ExpectType boolean
    result // $ExpectType boolean
  })
})
1 failed Ramda.both specs

💥 Reason for the failure: ramda 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) => readonly 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 the above R.chain example in Rambda REPL

All Typescript definitions
chain<T, U>(fn: (n: T) => readonly U[], listreadonly T[])U[];
chain<T, U>(fn: (n: T) => readonly 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.js'
 
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 duplicate = (n: number) => [ n, n ]
 
describe('chain', () => {
  it('without passing type', () => {
    const result = chain(duplicate, list)
    result // $ExpectType number[]
  })
 
  it('passing types', () => {
    const duplicateAndModify = (x: number) => [
      `||${x}||`,
      `||${x}||`
    ]
    const result = chain<number, string>(duplicateAndModify, list)
    const resultCurried = chain<number, string>(duplicateAndModify)(list)
    result // $ExpectType string[]
    resultCurried // $ExpectType string[]
  })
})

5 failed Ramda.chain specs

💥 Reason for the failure: ramda passes to chain if available | ramda 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 the above 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 (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('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)
})

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 the above R.clone example in Rambda REPL

All Typescript definitions
clone<T>(inputT)T;
clone<T>(inputReadonlyArray<T>)T[];
R.clone source
export function clone(input){
  const out = Array.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 arr = {
    a : 1,
    b : 2,
    c : 3,
    d : [ 1, 2, 3 ],
    e : new Date(),
  }
  expect(clone(arr)).toEqual(arr)
})
 
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 ])
})
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(-4.5), -4.5);
    eq(R.clone(0.0), 0.0);
  });
  it('clones strings', function() {
    eq(R.clone('ramda'), 'ramda');
  });
  it('clones booleans', function() {
    eq(R.clone(true), true);
  });
});
describe('deep clone objects', function() {
  it('clones objects with circular references', function() {
    var x = {c: null};
    var y = {a: x};
    var z = {b: y};
    x.c = z;
    var clone = R.clone(x);
    assert.notStrictEqual(x, clone);
    assert.notStrictEqual(x.c, clone.c);
    assert.notStrictEqual(x.c.b, clone.c.b);
    assert.notStrictEqual(x.c.b.a, clone.c.b.a);
    assert.notStrictEqual(x.c.b.a.c, clone.c.b.a.c);
    eq(R.keys(clone), R.keys(x));
    eq(R.keys(clone.c), R.keys(x.c));
    eq(R.keys(clone.c.b), R.keys(x.c.b));
    eq(R.keys(clone.c.b.a), R.keys(x.c.b.a));
    eq(R.keys(clone.c.b.a.c), R.keys(x.c.b.a.c));
    x.c.b = 1;
    assert.notDeepEqual(clone.c.b, x.c.b);
  });
});
describe('deep clone arrays', function() {
});
describe('deep clone functions', function() {
});
describe('built-in types', function() {
  it('clones RegExp object', function() {
    R.forEach(function(pattern) {
      var clone = R.clone(pattern);
      assert.notStrictEqual(clone, pattern);
      eq(clone.constructor, RegExp);
      eq(clone.source, pattern.source);
      eq(clone.global, pattern.global);
      eq(clone.ignoreCase, pattern.ignoreCase);
      eq(clone.multiline, pattern.multiline);
    }, [/x/, /x/g, /x/i, /x/m, /x/gi, /x/gm, /x/im, /x/gim]);
  });
});
describe('deep clone deep nested mixed objects', function() {
  it('clones array with mutual ref object', function() {
    var obj = {a: 1};