11.1.0 • Published 26 days ago

rambdax v11.1.0

Weekly downloads
7,017
License
MIT
Repository
github
Last release
26 days ago

Rambdax

Extended version of Rambda(utility library) - Documentation

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

CircleCI codecov Library size install size

❯ Differences between Rambda and Rambdax

Rambdax passthrough all Rambda methods and introduce some new functions.

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

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

❯ Example use

import { composeAsync, filter, delay, mapAsync } from 'rambdax'

const result = await composeAsync(
  mapAsync(async x => {
    await delay(100)
    return x + 1
  }),
  filter(x => x > 1)
)([1, 2, 3])
// => [3, 4]

You can test this example in Rambda's REPL

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

❯ Rambdax's advantages

TypeScript included

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

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

Important - Rambdax version 9.0.0(or higher) requires TypeScript version 4.3.3(or higher).

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

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

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

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

Comma notation for R.pick and R.omit

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

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

Extendable with Ramda community projects

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

Understandable source code due to little usage of internals

Ramda uses a lot of internals, which hides a lot of logic. Reading the full source code of a method can be challenging.

Better VSCode experience

If the project is written in Javascript, then go to source definition action will lead you to actual implementation of the method.

Alternative TS definitions

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

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

❯ Missing Ramda methods

  • into
  • invert
  • invertObj
  • invoker
  • keysIn
  • lift
  • liftN
  • mapAccum
  • mapAccumRight
  • memoizeWith
  • mergeDeepWith
  • mergeDeepWithKey
  • mergeWithKey
  • nAry
  • nthArg
  • o
  • otherwise
  • pair
  • partialRight
  • pathSatisfies
  • pipeWith
  • project
  • promap
  • reduceRight
  • reduceWhile
  • reduced
  • remove
  • scan
  • sequence
  • splitWhenever
  • symmetricDifferenceWith
  • andThen
  • toPairsIn
  • unary
  • uncurryN
  • unfold
  • unionWith
  • until
  • useWith
  • valuesIn
  • xprod
  • thunkify
  • default

    Most of above methods are in progress to be added to Rambda. The following methods are not going to be added:

  • __ - placeholder method allows user to further customize the method call. While, it seems useful initially, the price is too high in terms of complexity for TypeScript definitions. If it is not easy exressable in TypeScript, it is not worth it as Rambda is a TypeScript first library.

  • construct - Using classes is not very functional programming oriented.
  • constructN - same as above
  • transduce - currently is out of focus
  • traverse - same as above

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

❯ Install

  • yarn add rambdax

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

https://unpkg.com/rambdax@CURRENT_VERSION/dist/rambdax.umd.js
  • with deno
import {add} from "https://deno.land/x/rambda/mod.ts";

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

Differences between Rambda and Ramda

  • Rambda's type detects async functions and unresolved Promises. The returned values are 'Async' and 'Promise'.

  • Rambda's type handles NaN input, in which case it returns NaN.

  • Rambda's forEach can iterate over objects not only arrays.

  • Rambda's map, filter, partition when they iterate over objects, they pass property and input object as predicate's argument.

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

  • Ramda's clamp work with strings, while Rambda's method work only with numbers.

  • Ramda's indexOf/lastIndexOf work with strings and lists, while Rambda's method work only with lists as iterable input.

  • Error handling, when wrong inputs are provided, may not be the same. This difference will be better documented once all brute force tests are completed.

  • TypeScript definitions between rambda and @types/ramda may vary.

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

❯ Benchmarks

There are methods which are benchmarked only with Ramda and Rambda(i.e. no Lodash).

Note that some of these methods, are called with and without curring. This is done in order to give more detailed performance feedback.

The benchmarks results are produced from latest versions of Rambda, Lodash(4.17.21) and Ramda(0.29.1).

methodRambdaRamdaLodash
add🚀 Fastest21.52% slower82.15% slower
adjust8.48% slower🚀 Fastest🔳
all🚀 Fastest7.18% slower🔳
allPass🚀 Fastest88.25% slower🔳
allPass🚀 Fastest98.56% slower🔳
and🚀 Fastest89.09% slower🔳
any🚀 Fastest92.87% slower45.82% slower
anyPass🚀 Fastest98.25% slower🔳
append🚀 Fastest2.07% slower🔳
applySpec🚀 Fastest80.43% slower🔳
assoc72.32% slower60.08% slower🚀 Fastest
clone🚀 Fastest91.86% slower86.48% slower
compose6.07% slower16.89% slower🚀 Fastest
converge78.63% slower🚀 Fastest🔳
curry🚀 Fastest28.86% slower🔳
curryN🚀 Fastest41.05% slower🔳
defaultTo🚀 Fastest48.91% slower🔳
drop🚀 Fastest82.35% slower🔳
dropLast🚀 Fastest86.74% slower🔳
equals58.37% slower96.73% slower🚀 Fastest
filter6.7% slower72.03% slower🚀 Fastest
find🚀 Fastest85.14% slower42.65% slower
findIndex🚀 Fastest86.48% slower72.27% slower
flatten🚀 Fastest85.68% slower3.57% slower
ifElse🚀 Fastest58.56% slower🔳
includes🚀 Fastest81.64% slower🔳
indexOf🚀 Fastest80.17% slower🔳
indexOf🚀 Fastest82.2% slower🔳
init🚀 Fastest92.24% slower13.3% slower
is🚀 Fastest57.69% slower🔳
isEmpty🚀 Fastest97.14% slower54.99% slower
last🚀 Fastest93.43% slower5.28% slower
lastIndexOf🚀 Fastest85.19% slower🔳
map🚀 Fastest86.6% slower11.73% slower
match🚀 Fastest44.83% slower🔳
merge🚀 Fastest12.21% slower55.76% slower
none🚀 Fastest96.48% slower🔳
objOf🚀 Fastest38.05% slower🔳
omit🚀 Fastest69.95% slower97.34% slower
over🚀 Fastest56.23% slower🔳
path37.81% slower77.81% slower🚀 Fastest
pick🚀 Fastest19.07% slower80.2% slower
pipe🚀 Fastest0.11% slower🔳
prop🚀 Fastest87.95% slower🔳
propEq🚀 Fastest91.92% slower🔳
range🚀 Fastest61.8% slower57.44% slower
reduce60.48% slower77.1% slower🚀 Fastest
repeat48.57% slower68.98% slower🚀 Fastest
replace33.45% slower33.99% slower🚀 Fastest
set🚀 Fastest50.35% slower🔳
sort🚀 Fastest40.23% slower🔳
sortBy🚀 Fastest25.29% slower56.88% slower
split🚀 Fastest55.37% slower17.64% slower
splitEvery🚀 Fastest71.98% slower🔳
take🚀 Fastest91.96% slower4.72% slower
takeLast🚀 Fastest93.39% slower19.22% slower
test🚀 Fastest82.34% slower🔳
type🚀 Fastest48.6% slower🔳
uniq🚀 Fastest84.9% slower🔳
uniqBy51.93% slower🚀 Fastest🔳
uniqWith8.29% slower🚀 Fastest🔳
uniqWith14.23% slower🚀 Fastest🔳
update🚀 Fastest52.35% slower🔳
view🚀 Fastest76.15% slower🔳

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

❯ Used by

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

API

add

It adds a and b.

:boom: It doesn't work with strings, as the inputs are parsed to numbers before calculation.

Try this R.add example in Rambda REPL

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

addIndex

Try this R.addIndex example in Rambda REPL

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

addIndexRight

Same as R.addIndex, but it will passed indexes are decreasing, instead of increasing.

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

adjust

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

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

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

Try this R.adjust example in Rambda REPL

import { cloneList } from './_internals/cloneList.js'
import { curry } from './curry.js'

function adjustFn(
  index, replaceFn, list
){
  const actualIndex = index < 0 ? list.length + index : index
  if (index >= list.length || actualIndex < 0) return list

  const clone = cloneList(list)
  clone[ actualIndex ] = replaceFn(clone[ actualIndex ])

  return clone
}

export const adjust = curry(adjustFn)
import { add } from './add.js'
import { adjust } from './adjust.js'
import { pipe } from './pipe.js'

const list = [ 0, 1, 2 ]
const expected = [ 0, 11, 2 ]

test('happy', () => {})

test('happy', () => {
  expect(adjust(
    1, add(10), list
  )).toEqual(expected)
})

test('with curring type 1 1 1', () => {
  expect(adjust(1)(add(10))(list)).toEqual(expected)
})

test('with curring type 1 2', () => {
  expect(adjust(1)(add(10), list)).toEqual(expected)
})

test('with curring type 2 1', () => {
  expect(adjust(1, add(10))(list)).toEqual(expected)
})

test('with negative index', () => {
  expect(adjust(
    -2, add(10), list
  )).toEqual(expected)
})

test('when index is out of bounds', () => {
  const list = [ 0, 1, 2, 3 ]
  expect(adjust(
    4, add(1), list
  )).toEqual(list)
  expect(adjust(
    -5, add(1), list
  )).toEqual(list)
})

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

all

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

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

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

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

Try this R.all example in Rambda REPL

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
}
import { all } from './all.js'

const list = [ 0, 1, 2, 3, 4 ]

test('when true', () => {
  const fn = x => x > -1

  expect(all(fn)(list)).toBeTrue()
})

test('when false', () => {
  const fn = x => x > 2

  expect(all(fn, list)).toBeFalse()
})

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

allFalse

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

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

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

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

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

Try this R.allFalse example in Rambda REPL

import { isTruthy } from './_internals/isTruthy.js'
import { type } from './type.js'

export function allFalse(...inputs){
  let counter = 0
  while (counter < inputs.length){
    const x = inputs[ counter ]

    if (type(x) === 'Function'){
      if (isTruthy(x())){
        return false
      }
    } else if (isTruthy(x)){
      return false
    }

    counter++
  }

  return true
}
import { runTests } from 'helpers-fn'

import { allFalse } from './allFalse.js'

const happy = { ok : [ () => false, () => [], () => {}, null, false, [] ] }
const withArray = { fail : [ ...happy.ok, [ 1 ] ] }
const withObject = { fail : [ ...happy.ok, { a : 1 } ] }
const withFunction = { fail : [ ...happy.ok, () => ({ a : 1 }) ] }
const withBoolean = { fail : [ ...happy.ok, true ] }

const testData = {
  label : 'R.allFalse',
  data  : [ happy, withArray, withObject, withFunction, withBoolean ],
  fn    : input => allFalse(...input),
}
runTests(testData)

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

allPass

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

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

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

Try this R.allPass example in Rambda REPL

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

    return true
  }
}
import { allPass } from './allPass.js'

test('happy', () => {
  const rules = [ x => typeof x === 'number', x => x > 10, x => x * 7 < 100 ]

  expect(allPass(rules)(11)).toBeTrue()

  expect(allPass(rules)(undefined)).toBeFalse()
})

test('when returns true', () => {
  const conditionArr = [ val => val.a === 1, val => val.b === 2 ]

  expect(allPass(conditionArr)({
    a : 1,
    b : 2,
  })).toBeTrue()
})

test('when returns false', () => {
  const conditionArr = [ val => val.a === 1, val => val.b === 3 ]

  expect(allPass(conditionArr)({
    a : 1,
    b : 2,
  })).toBeFalse()
})

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

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

allTrue

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

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

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

Try this R.allTrue example in Rambda REPL

import { isFalsy } from './_internals/isFalsy.js'
import { type } from './type.js'

export function allTrue(...inputs){
  let counter = 0
  while (counter < inputs.length){
    const x = inputs[ counter ]

    if (type(x) === 'Function'){
      if (isFalsy(x())){
        return false
      }
    } else if (isFalsy(x)){
      return false
    }

    counter++
  }

  return true
}
import { allTrue } from './allTrue.js'

test('with functions', () => {
  const foo = () => 1
  const bar = () => false
  const baz = () => JSON.parse('{sda')
  const result = allTrue(
    foo, bar, baz
  )
  expect(result).toBeFalse()
})

test('usage with non boolean', () => {
  const foo = { a : 1 }
  const baz = [ 1, 2, 3 ]

  const result = allTrue(
    foo, foo, baz
  )
  expect(result).toBeTrue()
})

test('usage with boolean', () => {
  const foo = 4
  const baz = [ 1, 2, 3 ]

  const result = allTrue(foo > 2, baz.length === 3)
  expect(result).toBeTrue()
})

test('escapes early - case 0', () => {
  const foo = undefined
  const result = allTrue(foo, () => foo.a)
  expect(result).toBeFalse()
})

test('escapes early - case 1', () => {
  const foo = null
  const result = allTrue(foo, () => foo.a)
  expect(result).toBeFalse()
})

test('escapes early - case 2', () => {
  const foo = { a : 'bar' }
  const result = allTrue(
    foo, foo.a, foo.a.b
  )
  expect(result).toBeFalse()
})

test('escapes early - case 3', () => {
  const foo = { a : { b : 'foo' } }
  const result = allTrue(
    foo,
    () => foo.a,
    () => foo.a.b
  )
  expect(result).toBeTrue()
})

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

allType

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

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

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

const targetType = 'String'

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

Try this R.allType example in Rambda REPL

import { type } from './type.js'

export function allType(targetType){
  return (...inputs) => {
    let counter = 0

    while (counter < inputs.length){
      if (type(inputs[ counter ]) !== targetType){
        return false
      }
      counter++
    }

    return true
  }
}
import { allType } from './allType.js'

test('when true', () => {
  const result = allType('Array')(
    [ 1, 2, 3 ], [], [ null ]
  )

  expect(result).toBeTrue()
})

test('when false', () => {
  const result = allType('String')(
    1, undefined, null, []
  )

  expect(result).toBeFalse()
})

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

always

It returns function that always returns x.

Try this R.always example in Rambda REPL

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

and

Logical AND

Try this R.and example in Rambda REPL

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

any

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

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

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

Try this R.any example in Rambda REPL

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
}
import { any } from './any.js'

const list = [ 1, 2, 3 ]

test('happy', () => {
  expect(any(x => x < 0, list)).toBeFalse()
})

test('with curry', () => {
  expect(any(x => x > 2)(list)).toBeTrue()
})

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

anyFalse

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

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

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

Try this R.anyFalse example in Rambda REPL

import { isFalsy } from './_internals/isFalsy.js'
import { type } from './type.js'

export function anyFalse(...inputs){
  let counter = 0
  while (counter < inputs.length){
    const x = inputs[ counter ]

    if (type(x) === 'Function'){
      if (isFalsy(x())){
        return true
      }
    } else if (isFalsy(x)){
      return true
    }

    counter++
  }

  return false
}
import { anyFalse } from './anyFalse.js'

test('when true', () => {
  expect(anyFalse(
    true, true, false
  )).toBeTruthy()
})

test('when false', () => {
  expect(anyFalse(true, true)).toBeFalsy()
})

test('supports function', () => {
  expect(anyFalse(
    true,
    () => true,
    () => false
  )).toBeTruthy()
})

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

anyPass

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

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

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

    return false
  }
}
import { anyPass } from './anyPass.js'

test('happy', () => {
  const rules = [ x => typeof x === 'string', x => x > 10 ]
  const predicate = anyPass(rules)
  expect(predicate('foo')).toBeTrue()
  expect(predicate(6)).toBeFalse()
})

test('happy', () => {
  const rules = [ x => typeof x === 'string', x => x > 10 ]

  expect(anyPass(rules)(11)).toBeTrue()
  expect(anyPass(rules)(undefined)).toBeFalse()
})

const obj = {
  a : 1,
  b : 2,
}

test('when returns true', () => {
  const conditionArr = [ val => val.a === 1, val => val.a === 2 ]

  expect(anyPass(conditionArr)(obj)).toBeTrue()
})

test('when returns false + curry', () => {
  const conditionArr = [ val => val.a === 2, val => val.b === 3 ]

  expect(anyPass(conditionArr)(obj)).toBeFalse()
})

test('with empty predicates list', () => {
  expect(anyPass([])(3)).toBeFalse()
})

test('works with multiple inputs', () => {
  const fn = function (
    w, x, y, z
  ){
    console.log(
      w, x, y, z
    )

    return w + x === y + z
  }
  expect(anyPass([ fn ])(
    3, 3, 3, 3
  )).toBeTrue()
})

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

anyTrue

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

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

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

Try this R.anyTrue example in Rambda REPL

import { isTruthy } from './_internals/isTruthy.js'
import { type } from './type.js'

export function anyTrue(...inputs){
  let counter = 0
  while (counter < inputs.length){
    const x = inputs[ counter ]

    if (type(x) === 'Function'){
      if (isTruthy(x())){
        return true
      }
    } else if (isTruthy(x)){
      return true
    }

    counter++
  }

  return false
}
import { anyTrue } from './anyTrue.js'

test('when true', () => {
  expect(anyTrue(
    true, true, false
  )).toBeTruthy()
})

test('when false', () => {
  expect(anyTrue(
    false, false, false
  )).toBeFalsy()
})

test('supports function', () => {
  expect(anyTrue(
    false,
    false,
    false,
    () => false,
    () => true
  )).toBeTruthy()
})

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

anyType

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

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

targetType is one of the possible returns of R.type

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

const targetType = 'String'

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

Try this R.anyType example in Rambda REPL

import { type } from './type.js'

export function anyType(targetType){
  return (...inputs) => {
    let counter = 0

    while (counter < inputs.length){
      if (type(inputs[ counter ]) === targetType){
        return true
      }
      counter++
    }

    return false
  }
}
import { anyType } from './anyType.js'

test('when true', () => {
  const result = anyType('Array')(
    1, undefined, null, []
  )

  expect(result).toBeTrue()
})

test('when false', () => {
  const result = anyType('String')(
    1, undefined, null, []
  )

  expect(result).toBeFalse()
})

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

ap

ap<T, U>(fns: Array<(a: T) => U>[], vs: T[]): U[]

It takes a list of functions and a list of values. Then it returns a list of values obtained by applying each function to each value.

const result = R.ap(
  [
    x => x + 1,
    x => x + 2,
  ],
  [1, 2, 3]
)
// => [2, 3, 4, 3, 4, 5]

Try this R.ap example in Rambda REPL

export function ap(functions, input){
  if (arguments.length === 1){
    return _inputs => ap(functions, _inputs)
  }

  return functions.reduce((acc, fn) => [ ...acc, ...input.map(fn) ], [])
}
import { ap } from './ap.js'

function mult2(x){
  return x * 2
}
function plus3(x){
  return x + 3
}

test('happy', () => {
  expect(ap([ mult2, plus3 ], [ 1, 2, 3 ])).toEqual([ 2, 4, 6, 4, 5, 6 ])
})

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

aperture

aperture<N extends number, T>(n: N, list: T[]): Array<Tuple<T, N>> | []

It returns a new list, composed of consecutive n-tuples from a list.

const result = R.aperture(2, [1, 2, 3, 4])
// => [[1, 2], [2, 3], [3, 4]]

Try this R.aperture example in Rambda REPL

export function aperture(step, list){
  if (arguments.length === 1){
    return _list => aperture(step, _list)
  }
  if (step > list.length) return []
  let idx = 0
  const limit = list.length - (step - 1)
  const acc = new Array(limit)
  while (idx < limit){
    acc[ idx ] = list.slice(idx, idx + step)
    idx += 1
  }

  return acc
}
import { aperture } from './aperture.js'

const list = [ 1, 2, 3, 4, 5, 6, 7 ]

test('happy', () => {
  expect(aperture(1, list)).toEqual([ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ], [ 6 ], [ 7 ] ])
  expect(aperture(2, list)).toEqual([
    [ 1, 2 ],
    [ 2, 3 ],
    [ 3, 4 ],
    [ 4, 5 ],
    [ 5, 6 ],
    [ 6, 7 ],
  ])
  expect(aperture(3, list)).toEqual([
    [ 1, 2, 3 ],
    [ 2, 3, 4 ],
    [ 3, 4, 5 ],
    [ 4, 5, 6 ],
    [ 5, 6, 7 ],
  ])
  expect(aperture(8, list)).toEqual([])
})

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

append

append<T>(xToAppend: T, iterable: T[]): T[]

It adds element x at the end of iterable.

const x = 'foo'

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

Try this R.append example in Rambda REPL

import { cloneList } from './_internals/cloneList.js'

export function append(x, input){
  if (arguments.length === 1) return _input => append(x, _input)

  if (typeof input === 'string') return input.split('').concat(x)

  const clone = cloneList(input)
  clone.push(x)

  return clone
}
import { append } from './append.js'

test('happy', () => {
  expect(append('tests', [ 'write', 'more' ])).toEqual([
    'write',
    'more',
    'tests',
  ])
})

test('append to empty array', () => {
  expect(append('tests')([])).toEqual([ 'tests' ])
})

test('with strings', () => {
  expect(append('o', 'fo')).toEqual([ 'f', 'o', 'o' ])
})

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

apply

apply<T = any>(fn: (...args: any[]) => T, args: any[]): T

It applies function fn to the list of arguments.

This is useful for creating a fixed-arity function from a variadic function. fn should be a bound function if context is significant.

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

Try this R.apply example in Rambda REPL

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

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

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

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

test('provides no way to specify context', () => {
  const obj = {
    method (){
      return this === obj
    },
  }
  expect(apply(obj.method, [])).toBeFalse()
  expect(apply(bind(obj.method, obj), [])).toBeTrue()
})

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

applyDiff

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

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

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

const obj = {a: {b:1, c:2}}
const rules = [
  {op: 'remove', path: 'a.c'},
  {op: 'add', path: 'a.d', value: 4},
  {op: 'update', path: 'a.b', value: 2},
]
const result = R.applyDiff(rules, Record<string, unknown>)
const expected = {a: {b: 2, d: 4}}

// => `result` is equal to `expected`

Try this R.applyDiff example in Rambda REPL

import { createPath } from './_internals/createPath.js'
import { assocPathFn } from './assocPath.js'
import { path as pathModule } from './path.js'
const ALLOWED_OPERATIONS = [ 'remove', 'add', 'update' ]

export function removeAtPath(path, obj){
  const p = createPath(path)

  const len = p.length
  if (len === 0) return
  if (len === 1) return delete obj[ p[ 0 ] ]
  if (len === 2) return delete obj[ p[ 0 ] ][ p[ 1 ] ]
  if (len === 3) return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ]
  if (len === 4) return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ]
  if (len === 5) return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ]
  if (len === 6)
    return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ]

  if (len === 7)
    return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ]

  if (len === 8)
    return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ][ p[ 7 ] ]

  if (len === 9)
    return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ][ p[ 7 ] ][ p[ 8 ] ]

  if (len === 10)
    return delete obj[ p[ 0 ] ][ p[ 1 ] ][ p[ 2 ] ][ p[ 3 ] ][ p[ 4 ] ][ p[ 5 ] ][ p[ 6 ] ][ p[ 7 ] ][ p[ 8 ] ][
      p[ 9 ]
    ]

}

export function applyDiff(rules, obj){
  if (arguments.length === 1) return _obj => applyDiff(rules, _obj)

  let clone = { ...obj }

  rules.forEach(({ op, path, value }) => {
    if (!ALLOWED_OPERATIONS.includes(op)) return
    if (op === 'add' && path && value !== undefined){
      if (pathModule(path, obj)) return

      clone = assocPathFn(
        path, value, clone
      )

      return
    }

    if (op === 'remove'){
      if (pathModule(path, obj) === undefined) return

      removeAtPath(path, clone)

      return
    }
    if (op === 'update' && path && value !== undefined){
      if (pathModule(path, obj) === undefined) return

      clone = assocPathFn(
        path, value, clone
      )

    }
  })

  return clone
}
import { applyDiff } from './applyDiff.js'

test('remove operation', () => {
  const rules = [
    {
      op   : 'remove',
      path : 'a.b',
    },
  ]
  const result = applyDiff(rules, {
    a : {
      b : 1,
      c : 2,
    },
  })
  expect(result).toEqual({ a : { c : 2 } })
})

test('update operation', () => {
  const rules = [
    {
      op    : 'update',
      path  : 'a.b',
      value : 3,
    },
    {
      op    : 'update',
      path  : 'a.c.1',
      value : 3,
    },
    {
      op    : 'update',
      path  : 'a.d',
      value : 3,
    },
  ]
  expect(applyDiff(rules, {
    a : {
      b : 1,
      c : [ 1, 2 ],
    },
  })).toEqual({
    a : {
      b : 3,
      c : [ 1, 3 ],
    },
  })
})

test('add operation', () => {
  const rules = [
    {
      op    : 'add',
      path  : 'a.b',
      value : 3,
    },
    {
      op    : 'add',
      path  : 'a.d',
      value : 3,
    },
  ]
  const result = applyDiff(rules, {
    a : {
      b : 1,
      c : 2,
    },
  })

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

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

applySpec

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

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

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

Try this R.applySpec example in Rambda REPL

import { isArray } from './_internals/isArray.js'

// recursively traverse the given spec object to find the highest arity function
export 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
}
import { applySpec as applySpecRamda, nAry } from 'ramda'

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

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).toHaveLength(0)
  expect(fRamda).toHaveLength(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',
  })
})

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

applyTo

Try this R.applyTo example in Rambda REPL

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

ascend

Try this R.ascend example in Rambda REPL

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

assoc

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

:boom: This copies and flattens prototype properties onto the new object as well. All non-primitive properties are copied by reference.

Try this R.assoc example in Rambda REPL

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

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, Record<string, unknown>)
// => { a : 1, b : { c : 2 }}

Try this R.assocPath example in Rambda REPL

import { cloneList } from './_internals/cloneList.js'
import { createPath } from './_internals/createPath.js'
import { isArray } from './_internals/isArray.js'
import { isIndexInteger } from './_internals/isInteger.js'
import { assocFn } from './assoc.js'
import { curry } from './curry.js'

export function assocPathFn(
  path, newValue, input
){
  const pathArrValue = createPath(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 ?
      isIndexInteger(pathArrValue[ 1 ]) ?
        [] :
        {} :
      input[ index ]

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

  if (isIndexInteger(index) && isArray(input)){
    const arr = cloneList(input)
    arr[ index ] = newValue

    return arr
  }

  return assocFn(
    index, newValue, input
  )
}

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

test.only('happy', () => {
  const path = 'a.c.1'
  const input = {
    a : {
      b : 1,
      c : [ 1, 2 ],
    },
  }
  assocPathFn(
    path, 3, input
  )
  expect(input).toEqual({
    a : {
      b : 1,
      c : [ 1, 2 ],
    },
  })
})

test('string can be used as path input', () => {
  const testObj = {
    a : [ { b : 1 }, { b : 2 } ],
    d : 3,
  }
  const result1 = assocPathFn(
    [ 'a', 0, 'b' ], 10, testObj
  )
  const result2 = assocPathFn(
    'a.0.b', 10, testObj
  )

  const expected = {
    a : [ { b : 10 }, { b : 2 } ],
    d : 3,
  }
  expect(result1).toEqual(expected)
  expect(result2).toEqual(expected)
})

test('difference with ramda - doesn\'t overwrite primitive values with keys in the path', () => {
  const obj = { a : 'str' }
  const result = assocPath(
    [ 'a', 'b' ], 42, obj
  )

  expect(result).toEqual({
    a : {
      0 : 's',
      1 : 't',
      2 : 'r',
      b : 42,
    },
  })
})

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).toBe(3)
})

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

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

binary

Try this R.binary example in Rambda REPL

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

bind

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

Creates a function that is bound to a context.

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

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

Try this R.bind example in Rambda REPL

import { curryN } from './curryN.js'

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

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

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

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

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

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

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

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

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

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

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

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

both

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

It returns a function with input argument.

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

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

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

Try this R.both example in Rambda REPL

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

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

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

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

call

<a title="redirect to Rambda Repl site" href="

11.1.0

26 days ago

11.0.0

3 months ago

10.1.0

5 months ago

10.0.0

10 months ago

9.1.1

1 year ago

9.1.0

1 year ago

9.0.0

1 year ago

8.1.0

2 years ago

8.0.1

2 years ago

8.0.0

2 years ago

7.4.1

3 years ago

7.4.0

3 years ago

7.3.0

3 years ago

7.2.0

3 years ago

7.1.0

3 years ago

7.0.1

4 years ago

7.0.0

4 years ago

6.2.0

4 years ago

6.1.0

4 years ago

6.0.0

4 years ago

5.1.0

4 years ago

5.0.0

4 years ago

4.2.0

4 years ago

4.1.0

4 years ago

4.0.1

4 years ago

4.0.0

4 years ago

3.7.0

4 years ago

3.6.0

4 years ago

3.5.0

4 years ago

3.4.0

4 years ago

3.3.1

4 years ago

3.3.0

4 years ago

3.2.0

4 years ago

3.1.0

4 years ago

3.0.3

5 years ago

3.0.2

5 years ago

3.0.1

5 years ago

3.0.0

5 years ago

2.17.0

5 years ago

2.16.0

5 years ago

2.15.1

5 years ago

2.15.0

5 years ago

2.14.1

5 years ago

2.14.0

5 years ago

2.13.1

5 years ago

2.13.0

5 years ago

2.12.3

5 years ago

2.12.2

5 years ago

2.12.0

5 years ago

2.11.1

5 years ago

2.11.0

5 years ago

2.10.2

5 years ago

2.10.1

5 years ago

2.10.0

5 years ago

2.9.1

5 years ago

2.9.0

5 years ago

2.8.3

5 years ago

2.8.1

5 years ago

2.8.0

5 years ago

2.7.1

5 years ago

2.7.0

5 years ago

2.6.2

5 years ago

2.6.1

5 years ago

2.6.0

5 years ago

2.5.2

5 years ago

2.5.1

5 years ago

2.5.0

5 years ago

2.4.0

5 years ago

2.3.0

5 years ago

2.2.1

5 years ago

2.2.0

5 years ago

2.1.0

5 years ago

2.0.0

5 years ago

1.9.0

5 years ago

1.8.2

5 years ago

1.8.1

5 years ago

1.8.0

5 years ago

1.7.1

5 years ago

1.7.0

5 years ago

1.6.3

5 years ago

1.6.2

5 years ago

1.6.0

5 years ago

1.5.6

5 years ago

1.5.5

5 years ago

1.5.4

5 years ago

1.5.3

5 years ago

1.5.2

5 years ago

1.5.1

5 years ago

1.5.0

5 years ago

1.4.1

5 years ago

1.4.0

5 years ago

1.3.0

5 years ago

1.2.0

5 years ago

1.1.0

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago

0.24.0

5 years ago

0.23.0

5 years ago

0.22.0

5 years ago

0.21.0

5 years ago

0.20.1

5 years ago

0.20.0

5 years ago

0.19.0

6 years ago

0.18.0

6 years ago

0.17.0

6 years ago

0.16.0

6 years ago

0.15.3

6 years ago

0.15.2

6 years ago

0.15.1

6 years ago

0.15.0

6 years ago

0.14.1

6 years ago

0.14.0

6 years ago

0.13.9

6 years ago

0.13.8

6 years ago

0.13.7

6 years ago

0.13.6

6 years ago

0.13.5

6 years ago

0.13.4

6 years ago

0.13.3

6 years ago

0.13.2

6 years ago

0.13.1

6 years ago

0.13.0

6 years ago

0.12.9

6 years ago

0.12.8

6 years ago

0.12.7

6 years ago

0.12.6

6 years ago

0.12.5

6 years ago

0.12.4

6 years ago

0.12.3

6 years ago

0.12.2

6 years ago

0.12.1

6 years ago

0.12.0

6 years ago

0.11.2

6 years ago

0.11.1

6 years ago

0.11.0

6 years ago

0.10.1

6 years ago

0.10.0

6 years ago

0.9.1

6 years ago

0.9.0

6 years ago

0.8.10

6 years ago

0.8.9

6 years ago

0.8.8

6 years ago

0.8.7

6 years ago

0.8.6

6 years ago

0.8.5

6 years ago

0.8.4

6 years ago

0.8.3

6 years ago

0.8.2

6 years ago

0.8.1

6 years ago

0.8.0

6 years ago

0.7.8

6 years ago

0.7.7

6 years ago

0.7.6

6 years ago

0.7.5

6 years ago

0.7.4

6 years ago

0.7.0

6 years ago

0.6.10

6 years ago

0.6.9

7 years ago

0.6.8

7 years ago

0.6.7

7 years ago

0.6.5

7 years ago

0.6.4

7 years ago

0.6.3

7 years ago

0.6.2

7 years ago

0.6.1

7 years ago

0.6.0

7 years ago

0.5.12

7 years ago

0.5.11

7 years ago

0.5.10

7 years ago

0.5.9

7 years ago

0.5.8

7 years ago

0.5.7

7 years ago

0.5.6

7 years ago

0.5.5

7 years ago

0.5.4

7 years ago

0.5.3

7 years ago

0.5.2

7 years ago

0.5.1

7 years ago

0.5.0

7 years ago

0.4.10

7 years ago

0.4.9

7 years ago

0.4.8

7 years ago

0.4.7

7 years ago

0.4.6

7 years ago

0.4.5

7 years ago

0.4.4

7 years ago

0.4.3

7 years ago

0.4.2

7 years ago

0.4.1

7 years ago

0.4.0

7 years ago

0.3.20

7 years ago

0.3.19

7 years ago

0.3.18

7 years ago

0.3.17

7 years ago

0.3.16

7 years ago

0.3.15

7 years ago

0.3.14

7 years ago

0.3.13

7 years ago

0.3.12

7 years ago

0.3.11

7 years ago

0.3.10

7 years ago

0.3.9

7 years ago

0.3.7

7 years ago

0.3.6

7 years ago

0.3.4

7 years ago

0.3.3

7 years ago

0.3.2

7 years ago

0.3.0

7 years ago

0.1.2

7 years ago

0.1.1

7 years ago

0.2.2

7 years ago

0.2.1

7 years ago

0.2.0

7 years ago

0.1.0

7 years ago