Wondering what’s next for npm?Check out our public roadmap! »

rambdaTypeScript icon, indicating that this package has built-in type declarations

6.5.2 • 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 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.20) and Ramda(0.27.1).

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(a: number, b: number): 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(a: number, b: number): number;
add(a: number): (b: number) => number;
R.add source
export function add(a, b){
  if (arguments.length === 1) return _b => add(a, _b)

  return Number(a) + Number(b)
}
Tests
import { add } from './add'

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

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

adjust

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

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

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

Try this R.adjust example in Rambda REPL

All Typescript definitions
adjust<T>(index: number, replaceFn: (x: T) => T, list: readonly T[]): readonly T[];
adjust<T>(index: number, replaceFn: (x: T) => T): (list: readonly T[]) => readonly 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, list: readonly T[]): boolean

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

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

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

Try this R.all example in Rambda REPL

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

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

allPass

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

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

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: readonly ((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()
})
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>(x: T): () => 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>(x: T): () => 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()
})

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

and

and<T, U>(x: T, y: U): 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>(x: T, y: U): T | U;
and<T>(x: T): <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()
})

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

any

any<T>(predicate: (x: T) => boolean, list: readonly T[]): boolean

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

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, list: readonly 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()
})

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

anyPass

anyPass<T>(predicates: readonly SafePred<T>[]): SafePred<T>

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

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>(predicates: readonly 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)
})
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>(x: T, list: readonly T[]): readonly 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>(x: T, list: readonly T[]): readonly T[];
append<T>(x: T): <T>(list: readonly T[]) => readonly 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' ])
})

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

applySpec

applySpec<Spec extends Record<string, (...args: readonly any[]) => any>>(
  spec: Spec
): (
  ...args: Parameters<ValueOfRecord<Spec>>
) => { readonly [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
): (
  ...args: Parameters<ValueOfRecord<Spec>>
) => { readonly [Key in keyof Spec]: ReturnType<Spec[Key]> };
applySpec<T>(spec: any): (...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 (i < l){
    if (typeof arguments[ i ] === 'undefined') break
    defined[ i ] = arguments[ i ]
    i++
  }

  return defined
}

function __applySpecWithArity(
  spec, arity, cache
){
  const remaining = arity - cache.length

  if (remaining === 1)
    return x =>
      __applySpecWithArity(
        spec, arity, __filterUndefined(...cache, x)
      )
  if (remaining === 2)
    return (x, y) =>
      __applySpecWithArity(
        spec, arity, __filterUndefined(
          ...cache, x, y
        )
      )
  if (remaining === 3)
    return (
      x, y, z
    ) =>
      __applySpecWithArity(
        spec, arity, __filterUndefined(
          ...cache, x, y, z
        )
      )
  if (remaining === 4)
    return (
      x, y, z, a
    ) =>
      __applySpecWithArity(
        spec,
        arity,
        __filterUndefined(
          ...cache, x, y, z, a
        )
      )
  if (remaining > 4)
    return (...args) =>
      __applySpecWithArity(
        spec, arity, __filterUndefined(...cache, ...args)
      )

  // handle spec as Array
  if (_isArray(spec)){
    const ret = []
    let i = 0
    const l = spec.length
    for (; i < l; i++){
      // handle recursive spec inside array
      if (typeof spec[ i ] === 'object' || _isArray(spec[ i ])){
        ret[ i ] = __applySpecWithArity(
          spec[ i ], arity, cache
        )
      }
      // apply spec to the key
      if (typeof spec[ i ] === 'function'){
        ret[ i ] = spec[ i ](...cache)
      }
    }

    return ret
  }

  // handle spec as Object
  const ret = {}
  // apply callbacks to each property in the spec object
  for (const key in spec){
    if (spec.hasOwnProperty(key) === false || key === 'constructor') continue

    // apply the spec recursively
    if (typeof spec[ key ] === 'object'){
      ret[ key ] = __applySpecWithArity(
        spec[ key ], arity, cache
      )
      continue
    }

    // apply spec to the key
    if (typeof spec[ key ] === 'function'){
      ret[ key ] = spec[ key ](...cache)
    }
  }

  return ret
}

export function applySpec(spec, ...args){
  // get the highest arity spec function, cache the result and pass to __applySpecWithArity
  const arity = __findHighestArity(spec)

  if (arity === 0){
    return () => ({})
  }
  const toReturn = __applySpecWithArity(
    spec, arity, args
  )

  return toReturn
}
Tests
import { applySpec as applySpecRamda, nAry } from 'ramda'

import { add, always, compose, dec, inc, map, path, prop, T } from '../rambda'
import { applySpec } from './applySpec'

test('different than Ramda when bad spec', () => {
  const result = applySpec({ sum : { a : 1 } })(1, 2)
  const ramdaResult = applySpecRamda({ sum : { a : 1 } })(1, 2)
  expect(result).toEqual({})
  expect(ramdaResult).toEqual({ sum : { a : {} } })
})

test('works with empty spec', () => {
  expect(applySpec({})()).toEqual({})
  expect(applySpec([])(1, 2)).toEqual({})
  expect(applySpec(null)(1, 2)).toEqual({})
})

test('works with unary functions', () => {
  const result = applySpec({
    v : inc,
    u : dec,
  })(1)
  const expected = {
    v : 2,
    u : 0,
  }
  expect(result).toEqual(expected)
})

test('works with binary functions', () => {
  const result = applySpec({ sum : add })(1, 2)
  expect(result).toEqual({ sum : 3 })
})

test('works with nested specs', () => {
  const result = applySpec({
    unnested : always(0),
    nested   : { sum : add },
  })(1, 2)
  const expected = {
    unnested : 0,
    nested   : { sum : 3 },
  }
  expect(result).toEqual(expected)
})

test('works with arrays of nested specs', () => {
  const result = applySpec({
    unnested : always(0),
    nested   : [ { sum : add } ],
  })(1, 2)

  expect(result).toEqual({
    unnested : 0,
    nested   : [ { sum : 3 } ],
  })
})

test('works with arrays of spec objects', () => {
  const result = applySpec([ { sum : add } ])(1, 2)

  expect(result).toEqual([ { sum : 3 } ])
})

test('works with arrays of functions', () => {
  const result = applySpec([ map(prop('a')), map(prop('b')) ])([
    {
      a : 'a1',
      b : 'b1',
    },
    {
      a : 'a2',
      b : 'b2',
    },
  ])
  const expected = [
    [ 'a1', 'a2' ],
    [ 'b1', 'b2' ],
  ]
  expect(result).toEqual(expected)
})

test('works with a spec defining a map key', () => {
  expect(applySpec({ map : prop('a') })({ a : 1 })).toEqual({ map : 1 })
})

test('cannot retains the highest arity', () => {
  const f = applySpec({
    f1 : nAry(2, T),
    f2 : nAry(5, T),
  })
  const fRamda = applySpecRamda({
    f1 : nAry(2, T),
    f2 : nAry(5, T),
  })
  expect(f.length).toBe(0)
  expect(fRamda.length).toBe(5)
})

test('returns a curried function', () => {
  expect(applySpec({ sum : add })(1)(2)).toEqual({ sum : 3 })
})

// Additional tests
// ============================================
test('arity', () => {
  const spec = {
    one   : x1 => x1,
    two   : (x1, x2) => x1 + x2,
    three : (
      x1, x2, x3
    ) => x1 + x2 + x3,
  }
  expect(applySpec(
    spec, 1, 2, 3
  )).toEqual({
    one   : 1,
    two   : 3,
    three : 6,
  })
})

test('arity over 5 arguments', () => {
  const spec = {
    one   : x1 => x1,
    two   : (x1, x2) => x1 + x2,
    three : (
      x1, x2, x3
    ) => x1 + x2 + x3,
    four : (
      x1, x2, x3, x4
    ) => x1 + x2 + x3 + x4,
    five : (
      x1, x2, x3, x4, x5
    ) => x1 + x2 + x3 + x4 + x5,
  }
  expect(applySpec(
    spec, 1, 2, 3, 4, 5
  )).toEqual({
    one   : 1,
    two   : 3,
    three : 6,
    four  : 10,
    five  : 15,
  })
})

test('curried', () => {
  const spec = {
    one   : x1 => x1,
    two   : (x1, x2) => x1 + x2,
    three : (
      x1, x2, x3
    ) => x1 + x2 + x3,
  }
  expect(applySpec(spec)(1)(2)(3)).toEqual({
    one   : 1,
    two   : 3,
    three : 6,
  })
})

test('curried over 5 arguments', () => {
  const spec = {
    one   : x1 => x1,
    two   : (x1, x2) => x1 + x2,
    three : (
      x1, x2, x3
    ) => x1 + x2 + x3,
    four : (
      x1, x2, x3, x4
    ) => x1 + x2 + x3 + x4,
    five : (
      x1, x2, x3, x4, x5
    ) => x1 + x2 + x3 + x4 + x5,
  }
  expect(applySpec(spec)(1)(2)(3)(4)(5)).toEqual({
    one   : 1,
    two   : 3,
    three : 6,
    four  : 10,
    five  : 15,
  })
})

test('undefined property', () => {
  const spec = { prop : path([ 'property', 'doesnt', 'exist' ]) }
  expect(applySpec(spec, {})).toEqual({ prop : undefined })
})

test('restructure json object', () => {
  const spec = {
    id          : path('user.id'),
    name        : path('user.firstname'),
    profile     : path('user.profile'),
    doesntExist : path('user.profile.doesntExist'),
    info        : { views : compose(inc, prop('views')) },
    type        : always('playa'),
  }

  const data = {
    user : {
      id        : 1337,
      firstname : 'john',
      lastname  : 'shaft',
      profile   : 'shaft69',
    },
    views : 42,
  }

  expect(applySpec(spec, data)).toEqual({
    id          : 1337,
    name        : 'john',
    profile     : 'shaft69',
    doesntExist : undefined,
    info        : { views : 43 },
    type        : 'playa',
  })
})

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

assoc

assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & 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>(prop: K, val: T, obj: U): Record<K, T> & U;
assoc<T, K extends string>(prop: K, val: T): <U>(obj: U) => Record<K, T> & U;
assoc<K extends string>(prop: K): AssocPartialOne<K>;
R.assoc source
import { curry } from './curry'

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

export const assoc = curry(assocFn)
Tests
import { assoc } from './assoc'

test('adds a key to an empty object', () => {
  expect(assoc(
    'a', 1, {}
  )).toEqual({ a : 1 })
})

test('adds a key to a non-empty object', () => {
  expect(assoc(
    'b', 2, { a : 1 }
  )).toEqual({
    a : 1,
    b : 2,
  })
})

test('adds a key to a non-empty object - curry case 1', () => {
  expect(assoc('b', 2)({ a : 1 })).toEqual({
    a : 1,
    b : 2,
  })
})

test('adds a key to a non-empty object - curry case 2', () => {
  expect(assoc('b')(2, { a : 1 })).toEqual({
    a : 1,
    b : 2,
  })
})

test('adds a key to a non-empty object - curry case 3', () => {
  const result = assoc('b')(2)({ a : 1 })

  expect(result).toEqual({
    a : 1,
    b : 2,
  })
})

test('changes an existing key', () => {
  expect(assoc(
    'a', 2, { a : 1 }
  )).toEqual({ a : 2 })
})

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

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

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

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

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

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

assocPath

assocPath<Output>(path: Path, newValue: any, obj: object): Output

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

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

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

Try this R.assocPath example in Rambda REPL

All Typescript definitions
assocPath<Output>(path: Path, newValue: any, obj: object): Output;
assocPath<Output>(

Install

npm i rambda

DownloadsWeekly Downloads

19,780

Version

6.5.2

License

MIT

Unpacked Size

1.1 MB

Total Files

444

Last publish

Collaborators

  • avatar