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

    6.9.0 • 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.

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

    Smaller size

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

    Tree-shaking

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

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

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

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

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

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

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

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

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

    Comma notation for R.pick and R.omit

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

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

    Speed

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

    Support

    Most of the valid issues are fixed within 2-3 days.

    Closing the issue is usually accompanied by publishing a new patch version of Rambda to NPM.

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

    ❯ Missing Ramda methods

    Click to see the full list of 88 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
    • memoizeWith
    • mergeDeepLeft
    • mergeDeepWith
    • mergeDeepWithKey
    • mergeRight
    • mergeWith
    • mergeWithKey
    • nAry
    • nthArg
    • o
    • 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 🚀 Fastest 26.91% slower 85.31% slower
    adjust 🚀 Fastest 3.2% slower 🔳
    all 🚀 Fastest 93.1% slower 🔳
    allPass 🚀 Fastest 98.56% slower 🔳
    and 🚀 Fastest 89.09% slower 🔳
    any 🚀 Fastest 92.87% slower 45.82% slower
    anyPass 🚀 Fastest 98.25% slower 🔳
    append 🚀 Fastest 2.07% slower 🔳
    applySpec 🚀 Fastest 80.43% slower 🔳
    assoc 72.32% slower 60.08% slower 🚀 Fastest
    clone 🚀 Fastest 91.86% slower 86.48% slower
    compose 🚀 Fastest 93.76% slower 72.65% slower
    converge 78.63% slower 🚀 Fastest 🔳
    curry 🚀 Fastest 28.86% slower 🔳
    curryN 🚀 Fastest 41.05% slower 🔳
    defaultTo 🚀 Fastest 48.91% slower 🔳
    drop 🚀 Fastest 82.35% slower 🔳
    dropLast 🚀 Fastest 86.74% slower 🔳
    equals 58.37% slower 96.73% slower 🚀 Fastest
    filter 6.7% slower 72.03% slower 🚀 Fastest
    find 🚀 Fastest 85.14% slower 42.65% slower
    findIndex 🚀 Fastest 86.48% slower 72.27% slower
    flatten 🚀 Fastest 95.26% slower 10.27% slower
    ifElse 🚀 Fastest 58.56% slower 🔳
    includes 6.14% slower 🚀 Fastest 🔳
    indexOf 🚀 Fastest 82.2% slower 🔳
    init 🚀 Fastest 92.24% slower 13.3% slower
    is 🚀 Fastest 57.69% slower 🔳
    isEmpty 🚀 Fastest 97.14% slower 54.99% slower
    last 🚀 Fastest 93.43% slower 5.28% slower
    lastIndexOf 🚀 Fastest 85.19% slower 🔳
    map 🚀 Fastest 86.6% slower 11.73% slower
    match 🚀 Fastest 44.83% slower 🔳
    merge 🚀 Fastest 12.21% slower 55.76% slower
    none 🚀 Fastest 96.48% slower 🔳
    objOf 🚀 Fastest 38.05% slower 🔳
    omit 🚀 Fastest 69.95% slower 97.34% slower
    over 🚀 Fastest 56.23% slower 🔳
    path 37.81% slower 77.81% slower 🚀 Fastest
    pick 🚀 Fastest 19.07% slower 80.2% slower
    prop 🚀 Fastest 87.95% slower 🔳
    propEq 🚀 Fastest 91.92% slower 🔳
    range 🚀 Fastest 61.8% slower 57.44% slower
    reduce 60.48% slower 77.1% slower 🚀 Fastest
    repeat 48.57% slower 68.98% slower 🚀 Fastest
    replace 33.45% slower 33.99% slower 🚀 Fastest
    set 🚀 Fastest 50.35% slower 🔳
    sort 🚀 Fastest 44.29% slower 🔳
    sortBy 🚀 Fastest 25.29% slower 56.88% slower
    split 🚀 Fastest 55.37% slower 17.64% slower
    splitEvery 🚀 Fastest 71.98% slower 🔳
    take 🚀 Fastest 91.96% slower 4.72% slower
    takeLast 🚀 Fastest 93.39% slower 19.22% slower
    test 🚀 Fastest 82.34% slower 🔳
    type 🚀 Fastest 48.6% slower 🔳
    uniq 🚀 Fastest 88.46% slower 🔳
    uniqWith 14.23% slower 🚀 Fastest 🔳
    update 🚀 Fastest 52.35% slower 🔳
    view 🚀 Fastest 76.15% slower 🔳

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

    ❯ Used by

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

    API

    add

    add(a: number, b: number): number

    It adds a and b.

    All Typescript definitions
    add(a: number, b: number): number;
    add(a: number): (b: number) => number;
    R.add source
    export function add(a, b){
      if (arguments.length === 1) return _b => add(a, _b)
    
      return Number(a) + Number(b)
    }
    Tests
    import { add } from './add'
    
    test('with number', () => {
      expect(add(2, 3)).toEqual(5)
      expect(add(7)(10)).toEqual(17)
    })
    
    test('string is bad input', () => {
      expect(add('foo', 'bar')).toBeNaN()
    })
    
    test('ramda specs', () => {
      expect(add('1', '2')).toEqual(3)
      expect(add(1, '2')).toEqual(3)
      expect(add(true, false)).toEqual(1)
      expect(add(null, null)).toEqual(0)
      expect(add(undefined, undefined)).toEqual(NaN)
      expect(add(new Date(1), new Date(2))).toEqual(3)
    })
    Typescript test
    import {add} from 'rambda'
    
    describe('R.add', () => {
      it('happy', () => {
        const result = add(4, 1)
    
        result // $ExpectType number
      })
      it('curried', () => {
        const result = add(4)(1)
    
        result // $ExpectType number
      })
    })
    Rambda is fastest. Ramda is 26.91% slower and Lodash is 85.31% slower
    const R = require('../../dist/rambda.js')
    
    const add = [
      {
        label : 'Rambda',
        fn    : () => {
          R.add(1, 1)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.add(1, 1)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.add(1, 1)
        },
      },
    ]
    

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

    adjust

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

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

    All Typescript definitions
    adjust<T>(index: number, replaceFn: (x: T) => T, list: T[]): T[];
    adjust<T>(index: number, replaceFn: (x: T) => T): (list: T[]) => T[];
    R.adjust source
    import { curry } from './curry'
    
    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)
    })
    Rambda is faster than Ramda with 3.2%
    const R = require('../../dist/rambda.js')
    
    const list = [ 0, 1, 2 ]
    const fn = x => x + 1
    const index = 1
    
    const adjust = [
      {
        label : 'Rambda',
        fn    : () => {
          R.adjust(
            index, fn, list
          )
          R.adjust(index, fn)(list)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.adjust(
            index, fn, list
          )
          Ramda.adjust(index, fn)(list)
        },
      },
    ]
    

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

    all

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

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

    All Typescript definitions
    all<T>(predicate: (x: T) => boolean, list: T[]): boolean;
    all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
    R.all source
    export function all(predicate, list){
      if (arguments.length === 1) return _list => all(predicate, _list)
    
      for (let i = 0; i < list.length; i++){
        if (!predicate(list[ i ])) return false
      }
    
      return true
    }
    Tests
    import { all } from './all'
    
    const list = [ 0, 1, 2, 3, 4 ]
    
    test('when true', () => {
      const fn = x => x > -1
    
      expect(all(fn)(list)).toBeTrue()
    })
    
    test('when false', () => {
      const fn = x => x > 2
    
      expect(all(fn, list)).toBeFalse()
    })
    Typescript test
    import {all} from 'rambda'
    
    describe('all', () => {
      it('happy', () => {
        const result = all(
          x => {
            x // $ExpectType number
            return x > 0
          },
          [1, 2, 3]
        )
        result // $ExpectType boolean
      })
      it('curried needs a type', () => {
        const result = all<number>(x => {
          x // $ExpectType number
          return x > 0
        })([1, 2, 3])
        result // $ExpectType boolean
      })
    })
    Rambda is faster than Ramda with 93.1%
    const R = require('../../dist/rambda.js')
    
    const input = [ 1, 2, 3, 4 ]
    
    const all = [
      {
        label : 'Rambda',
        fn    : () => {
          const fn = x => x > 2
    
          R.all(fn, input)
          R.all(fn)(input)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          const fn = x => x > 2
    
          Ramda.all(fn, input)
          Ramda.all(fn)(input)
        },
      },
    ]
    

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

    allPass

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

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

    All Typescript definitions
    allPass<T>(predicates: ((x: T) => boolean)[]): (input: T) => boolean;
    R.allPass source
    export function allPass(predicates){
      return input => {
        let counter = 0
        while (counter < predicates.length){
          if (!predicates[ counter ](input)){
            return false
          }
          counter++
        }
    
        return true
      }
    }
    Tests
    import { allPass } from './allPass'
    
    test('happy', () => {
      const rules = [ x => typeof x === 'number', x => x > 10, x => x * 7 < 100 ]
    
      expect(allPass(rules)(11)).toBeTrue()
    
      expect(allPass(rules)(undefined)).toBeFalse()
    })
    
    test('when returns true', () => {
      const conditionArr = [ val => val.a === 1, val => val.b === 2 ]
    
      expect(allPass(conditionArr)({
        a : 1,
        b : 2,
      })).toBeTrue()
    })
    
    test('when returns false', () => {
      const conditionArr = [ val => val.a === 1, val => val.b === 3 ]
    
      expect(allPass(conditionArr)({
        a : 1,
        b : 2,
      })).toBeFalse()
    })
    Typescript test
    import {allPass} from 'rambda'
    
    describe('allPass', () => {
      it('happy', () => {
        const x = allPass<number>([
          y => {
            y // $ExpectType number
            return typeof y === 'number'
          },
          y => {
            return y > 0
          },
        ])(11)
    
        x // $ExpectType boolean
      })
    })
    Rambda is faster than Ramda with 98.56%
    const R = require('../../dist/rambda.js')
    
    const rules = [ x => typeof x === 'number', x => x > 10, x => x * 7 < 100 ]
    
    const allPass = [
      {
        label : 'Rambda',
        fn    : () => {
          R.allPass(rules)(11)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.allPass(rules)(11)
        },
      },
    ]
    

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

    always

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

    It returns function that always returns x.

    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()
    })
    Typescript test
    import {always} from 'rambda'
    
    describe('R.always', () => {
      it('happy', () => {
        const fn = always('foo')
        fn // $ExpectType () => string
        const result = fn()
        result // $ExpectType string
      })
    })

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

    and

    and<T, U>(x: T, y: U): T | U

    Logical AND

    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()
    })
    Typescript test
    import {and} from 'rambda'
    
    describe('R.and', () => {
      it('happy', () => {
        const result = and(true, false)
        result // $ExpectType boolean
      })
      it('curried', () => {
        const result = and('foo')(1)
        result // $ExpectType string | 1
      })
    })
    Rambda is faster than Ramda with 89.09%
    const R = require('../../dist/rambda.js')
    
    const and = [
      {
        label : 'Rambda',
        fn    : () => {
          R.and(true, true)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.and(true, true)
        },
      },
    ]
    

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

    any

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

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

    All Typescript definitions
    any<T>(predicate: (x: T) => boolean, list: T[]): boolean;
    any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
    R.any source
    export function any(predicate, list){
      if (arguments.length === 1) return _list => any(predicate, _list)
    
      let counter = 0
      while (counter < list.length){
        if (predicate(list[ counter ], counter)){
          return true
        }
        counter++
      }
    
      return false
    }
    Tests
    import { any } from './any'
    
    const list = [ 1, 2, 3 ]
    
    test('happy', () => {
      expect(any(x => x < 0, list)).toBeFalse()
    })
    
    test('with curry', () => {
      expect(any(x => x > 2)(list)).toBeTrue()
    })
    Typescript test
    import {any} from 'rambda'
    
    describe('R.any', () => {
      it('happy', () => {
        const result = any(
          x => {
            x // $ExpectType number
            return x > 2
          },
          [1, 2, 3]
        )
        result // $ExpectType boolean
      })
    
      it('when curried needs a type', () => {
        const result = any<number>(x => {
          x // $ExpectType number
          return x > 2
        })([1, 2, 3])
        result // $ExpectType boolean
      })
    })
    Rambda is fastest. Ramda is 92.87% slower and Lodash is 45.82% slower
    const R = require('../../dist/rambda.js')
    
    const input = [ 1, 2, 3, 4 ]
    const fn = val => val > 2
    
    const any = [
      {
        label : 'Rambda',
        fn    : () => {
          R.any(fn, input)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.any(fn, input)
        },
      },
      {
        label : 'Lodash.some',
        fn    : () => {
          _.some(input, fn)
        },
      },
    ]
    

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

    anyPass

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

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

    All Typescript definitions
    anyPass<T>(predicates: SafePred<T>[]): SafePred<T>;
    R.anyPass source
    export function anyPass(predicates){
      return input => {
        let counter = 0
        while (counter < predicates.length){
          if (predicates[ counter ](input)){
            return true
          }
          counter++
        }
    
        return false
      }
    }
    Tests
    import { anyPass } from './anyPass'
    
    test('happy', () => {
      const rules = [ x => typeof x === 'string', x => x > 10 ]
      const predicate = anyPass(rules)
      expect(predicate('foo')).toBeTrue()
      expect(predicate(6)).toBeFalse()
    })
    
    test('happy', () => {
      const rules = [ x => typeof x === 'string', x => x > 10 ]
    
      expect(anyPass(rules)(11)).toBeTrue()
    
      expect(anyPass(rules)(undefined)).toBeFalse()
    })
    
    const obj = {
      a : 1,
      b : 2,
    }
    
    test('when returns true', () => {
      const conditionArr = [ val => val.a === 1, val => val.a === 2 ]
    
      expect(anyPass(conditionArr)(obj)).toBeTrue()
    })
    
    test('when returns false + curry', () => {
      const conditionArr = [ val => val.a === 2, val => val.b === 3 ]
    
      expect(anyPass(conditionArr)(obj)).toBeFalse()
    })
    
    test('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
      })
    })
    Rambda is faster than Ramda with 98.25%
    const R = require('../../dist/rambda.js')
    
    const rules = [ x => typeof x === 'boolean', x => x > 20, x => x * 7 < 100 ]
    
    const anyPass = [
      {
        label : 'Rambda',
        fn    : () => {
          R.anyPass(rules)(11)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.anyPass(rules)(11)
        },
      },
    ]
    

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

    append

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

    It adds element x at the end of list.

    All Typescript definitions
    append<T>(x: T, list: T[]): T[];
    append<T>(x: T): <T>(list: T[]) => T[];
    R.append source
    export function append(x, input){
      if (arguments.length === 1) return _input => append(x, _input)
    
      if (typeof input === 'string') return input.split('').concat(x)
    
      const clone = input.slice()
      clone.push(x)
    
      return clone
    }
    Tests
    import { append } from './append'
    
    test('happy', () => {
      expect(append('tests', [ 'write', 'more' ])).toEqual([
        'write',
        'more',
        'tests',
      ])
    })
    
    test('append to empty array', () => {
      expect(append('tests')([])).toEqual([ 'tests' ])
    })
    
    test('with strings', () => {
      expect(append('o', 'fo')).toEqual([ 'f', 'o', 'o' ])
    })
    Typescript test
    import {append} from 'rambda'
    
    const list = [1, 2, 3]
    
    describe('R.append', () => {
      it('happy', () => {
        const result = append(4, list)
    
        result // $ExpectType number[]
      })
      it('curried', () => {
        const result = append(4)(list)
    
        result // $ExpectType number[]
      })
    })
    Rambda is faster than Ramda with 2.07%
    const R = require('../../dist/rambda.js')
    
    const append = [
      {
        label : 'Rambda',
        fn    : () => {
          R.append(0)([ 1, 2, 3, 4 ])
          R.append('bar')('foo')
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.append(0)([ 1, 2, 3, 4 ])
          Ramda.append('bar')('foo')
        },
      },
    ]
    

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

    applySpec

    applySpec<Spec extends Record<string, (...args: any[]) => any>>(
      spec: Spec
    ): (
      ...args: Parameters<ValueOfRecord<Spec>>
    ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> }
    All Typescript definitions
    applySpec<Spec extends Record<string, (...args: any[]) => any>>(
      spec: Spec
    ): (
      ...args: Parameters<ValueOfRecord<Spec>>
    ) => { [Key in keyof Spec]: ReturnType<Spec[Key]> };
    applySpec<T>(spec: any): (...args: any[]) => T;
    R.applySpec source
    import { _isArray } from './_internals/_isArray'
    
    // recursively traverse the given spec object to find the highest arity function
    function __findHighestArity(spec, max = 0){
      for (const key in spec){
        if (spec.hasOwnProperty(key) === false || key === 'constructor') continue
    
        if (typeof spec[ key ] === 'object'){
          max = Math.max(max, __findHighestArity(spec[ key ]))
        }
    
        if (typeof spec[ key ] === 'function'){
          max = Math.max(max, spec[ key ].length)
        }
      }
    
      return max
    }
    
    function __filterUndefined(){
      const defined = []
      let i = 0
      const l = arguments.length
      while (i < l){
        if (typeof arguments[ i ] === 'undefined') break
        defined[ i ] = arguments[ i ]
        i++
      }
    
      return defined
    }
    
    function __applySpecWithArity(
      spec, arity, cache
    ){
      const remaining = arity - cache.length
    
      if (remaining === 1)
        return x =>
          __applySpecWithArity(
            spec, arity, __filterUndefined(...cache, x)
          )
      if (remaining === 2)
        return (x, y) =>
          __applySpecWithArity(
            spec, arity, __filterUndefined(
              ...cache, x, y
            )
          )
      if (remaining === 3)
        return (
          x, y, z
        ) =>
          __applySpecWithArity(
            spec, arity, __filterUndefined(
              ...cache, x, y, z
            )
          )
      if (remaining === 4)
        return (
          x, y, z, a
        ) =>
          __applySpecWithArity(
            spec,
            arity,
            __filterUndefined(
              ...cache, x, y, z, a
            )
          )
      if (remaining > 4)
        return (...args) =>
          __applySpecWithArity(
            spec, arity, __filterUndefined(...cache, ...args)
          )
    
      // handle spec as Array
      if (_isArray(spec)){
        const ret = []
        let i = 0
        const l = spec.length
        for (; i < l; i++){
          // handle recursive spec inside array
          if (typeof spec[ i ] === 'object' || _isArray(spec[ i ])){
            ret[ i ] = __applySpecWithArity(
              spec[ i ], arity, cache
            )
          }
          // apply spec to the key
          if (typeof spec[ i ] === 'function'){
            ret[ i ] = spec[ i ](...cache)
          }
        }
    
        return ret
      }
    
      // handle spec as Object
      const ret = {}
      // apply callbacks to each property in the spec object
      for (const key in spec){
        if (spec.hasOwnProperty(key) === false || key === 'constructor') continue
    
        // apply the spec recursively
        if (typeof spec[ key ] === 'object'){
          ret[ key ] = __applySpecWithArity(
            spec[ key ], arity, cache
          )
          continue
        }
    
        // apply spec to the key
        if (typeof spec[ key ] === 'function'){
          ret[ key ] = spec[ key ](...cache)
        }
      }
    
      return ret
    }
    
    export function applySpec(spec, ...args){
      // get the highest arity spec function, cache the result and pass to __applySpecWithArity
      const arity = __findHighestArity(spec)
    
      if (arity === 0){
        return () => ({})
      }
      const toReturn = __applySpecWithArity(
        spec, arity, args
      )
    
      return toReturn
    }
    Tests
    import { applySpec as applySpecRamda, nAry } from 'ramda'
    
    import { add, always, compose, dec, inc, map, path, prop, T } from '../rambda'
    import { applySpec } from './applySpec'
    
    test('different than Ramda when bad spec', () => {
      const result = applySpec({ sum : { a : 1 } })(1, 2)
      const ramdaResult = applySpecRamda({ sum : { a : 1 } })(1, 2)
      expect(result).toEqual({})
      expect(ramdaResult).toEqual({ sum : { a : {} } })
    })
    
    test('works with empty spec', () => {
      expect(applySpec({})()).toEqual({})
      expect(applySpec([])(1, 2)).toEqual({})
      expect(applySpec(null)(1, 2)).toEqual({})
    })
    
    test('works with unary functions', () => {
      const result = applySpec({
        v : inc,
        u : dec,
      })(1)
      const expected = {
        v : 2,
        u : 0,
      }
      expect(result).toEqual(expected)
    })
    
    test('works with binary functions', () => {
      const result = applySpec({ sum : add })(1, 2)
      expect(result).toEqual({ sum : 3 })
    })
    
    test('works with nested specs', () => {
      const result = applySpec({
        unnested : always(0),
        nested   : { sum : add },
      })(1, 2)
      const expected = {
        unnested : 0,
        nested   : { sum : 3 },
      }
      expect(result).toEqual(expected)
    })
    
    test('works with arrays of nested specs', () => {
      const result = applySpec({
        unnested : always(0),
        nested   : [ { sum : add } ],
      })(1, 2)
    
      expect(result).toEqual({
        unnested : 0,
        nested   : [ { sum : 3 } ],
      })
    })
    
    test('works with arrays of spec objects', () => {
      const result = applySpec([ { sum : add } ])(1, 2)
    
      expect(result).toEqual([ { sum : 3 } ])
    })
    
    test('works with arrays of functions', () => {
      const result = applySpec([ map(prop('a')), map(prop('b')) ])([
        {
          a : 'a1',
          b : 'b1',
        },
        {
          a : 'a2',
          b : 'b2',
        },
      ])
      const expected = [
        [ 'a1', 'a2' ],
        [ 'b1', 'b2' ],
      ]
      expect(result).toEqual(expected)
    })
    
    test('works with a spec defining a map key', () => {
      expect(applySpec({ map : prop('a') })({ a : 1 })).toEqual({ map : 1 })
    })
    
    test('cannot retains the highest arity', () => {
      const f = applySpec({
        f1 : nAry(2, T),
        f2 : nAry(5, T),
      })
      const fRamda = applySpecRamda({
        f1 : nAry(2, T),
        f2 : nAry(5, T),
      })
      expect(f.length).toBe(0)
      expect(fRamda.length).toBe(5)
    })
    
    test('returns a curried function', () => {
      expect(applySpec({ sum : add })(1)(2)).toEqual({ sum : 3 })
    })
    
    // Additional tests
    // ============================================
    test('arity', () => {
      const spec = {
        one   : x1 => x1,
        two   : (x1, x2) => x1 + x2,
        three : (
          x1, x2, x3
        ) => x1 + x2 + x3,
      }
      expect(applySpec(
        spec, 1, 2, 3
      )).toEqual({
        one   : 1,
        two   : 3,
        three : 6,
      })
    })
    
    test('arity over 5 arguments', () => {
      const spec = {
        one   : x1 => x1,
        two   : (x1, x2) => x1 + x2,
        three : (
          x1, x2, x3
        ) => x1 + x2 + x3,
        four : (
          x1, x2, x3, x4
        ) => x1 + x2 + x3 + x4,
        five : (
          x1, x2, x3, x4, x5
        ) => x1 + x2 + x3 + x4 + x5,
      }
      expect(applySpec(
        spec, 1, 2, 3, 4, 5
      )).toEqual({
        one   : 1,
        two   : 3,
        three : 6,
        four  : 10,
        five  : 15,
      })
    })
    
    test('curried', () => {
      const spec = {
        one   : x1 => x1,
        two   : (x1, x2) => x1 + x2,
        three : (
          x1, x2, x3
        ) => x1 + x2 + x3,
      }
      expect(applySpec(spec)(1)(2)(3)).toEqual({
        one   : 1,
        two   : 3,
        three : 6,
      })
    })
    
    test('curried over 5 arguments', () => {
      const spec = {
        one   : x1 => x1,
        two   : (x1, x2) => x1 + x2,
        three : (
          x1, x2, x3
        ) => x1 + x2 + x3,
        four : (
          x1, x2, x3, x4
        ) => x1 + x2 + x3 + x4,
        five : (
          x1, x2, x3, x4, x5
        ) => x1 + x2 + x3 + x4 + x5,
      }
      expect(applySpec(spec)(1)(2)(3)(4)(5)).toEqual({
        one   : 1,
        two   : 3,
        three : 6,
        four  : 10,
        five  : 15,
      })
    })
    
    test('undefined property', () => {
      const spec = { prop : path([ 'property', 'doesnt', 'exist' ]) }
      expect(applySpec(spec, {})).toEqual({ prop : undefined })
    })
    
    test('restructure json object', () => {
      const spec = {
        id          : path('user.id'),
        name        : path('user.firstname'),
        profile     : path('user.profile'),
        doesntExist : path('user.profile.doesntExist'),
        info        : { views : compose(inc, prop('views')) },
        type        : always('playa'),
      }
    
      const data = {
        user : {
          id        : 1337,
          firstname : 'john',
          lastname  : 'shaft',
          profile   : 'shaft69',
        },
        views : 42,
      }
    
      expect(applySpec(spec, data)).toEqual({
        id          : 1337,
        name        : 'john',
        profile     : 'shaft69',
        doesntExist : undefined,
        info        : { views : 43 },
        type        : 'playa',
      })
    })
    Typescript test
    import {multiply, applySpec, inc, dec, add} from 'rambda'
    
    describe('applySpec', () => {
      it('ramda 1', () => {
        const result = applySpec({
          v: inc,
          u: dec,
        })(1)
        result // $ExpectType { v: number; u: number; }
      })
      it('ramda 1', () => {
        interface Output {
          sum: number,
          multiplied: number,
        }
        const result = applySpec<Output>({
          sum: add,
          multiplied: multiply,
        })(1, 2)
    
        result // $ExpectType Output
      })
    })
    Rambda is faster than Ramda with 80.43%
    const R = require('../../dist/rambda.js')
    
    const curryN = [
      {
        label : 'Rambda',
        fn    : () => {
          const data = {
            a : {
              b : { c : 1 },
              d : 2,
            },
          }
          const spec = {
            c : R.path([ 'a', 'b', 'c' ]),
            d : R.path([ 'a', 'd' ]),
          }
          R.applySpec(spec, data)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          const data = {
            a : {
              b : { c : 1 },
              d : 2,
            },
          }
          const spec = {
            c : Ramda.path([ 'a', 'b', 'c' ]),
            d : Ramda.path([ 'a', 'd' ]),
          }
          Ramda.applySpec(spec, data)
        },
      },
    ]
    

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

    assoc

    assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & U

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

    All Typescript definitions
    assoc<T, U, K extends string>(prop: K, val: T, obj: U): Record<K, T> & 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 } })
    })
    Typescript test
    import {assoc} from 'rambda'
    
    const obj = {a: 1}
    const newValue = 2
    const newProp = 'b'
    
    describe('R.assoc', () => {
      it('happy', () => {
        const result = assoc(newProp, newValue, obj)
    
        result.a // $ExpectType number
        result.b // $ExpectType number
      })
      it('curried 1', () => {
        const result = assoc(newProp, newValue)(obj)
    
        result.a // $ExpectType number
        result.b // $ExpectType number
      })
      it('curried 2', () => {
        const result = assoc(newProp)(newValue)(obj)
    
        result.a // $ExpectType number
        result.b // $ExpectType number
      })
    })
    Lodash is fastest. Rambda is 72.32% slower and Ramda is 60.08% slower
    const R = require('../../dist/rambda.js')
    
    const input = {
      a : 1,
      b : 2,
    }
    const key = 'c'
    const value = 3
    
    const assoc = [
      {
        label : 'Rambda',
        fn    : () => {
          R.assoc(
            key, value, input
          )
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.assoc(
            key, value, input
          )
        },
      },
      {
        label : 'Lodash.set',
        fn    : () => {
          _.set(
            input, key, value
          )
        },
      },
    ]
    

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

    assocPath

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

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

    All Typescript definitions
    assocPath<Output>(path: Path, newValue: any, obj: object): Output;
    assocPath<Output>(path: Path, newValue: any): (obj: object) => Output;
    assocPath<Output>(path: Path): (newValue: any) => (obj: object) => Output;
    R.assocPath source
    import { _isArray } from './_internals/_isArray'
    import { _isInteger } from './_internals/_isInteger'
    import { assoc } from './assoc'
    import { curry } from './curry'
    
    function assocPathFn(
      path, newValue, input
    ){
      const pathArrValue =
        typeof path === 'string' ?
          path.split('.').map(x => _isInteger(Number(x)) ? Number(x) : x) :
          path
      if (pathArrValue.length === 0){
        return newValue
      }
    
      const index = pathArrValue[ 0 ]
      if (pathArrValue.length > 1){
        const condition =
          typeof input !== 'object' ||
          input === null ||
          !input.hasOwnProperty(index)
    
        const nextinput = condition ?
          _isInteger(pathArrValue[ 1 ]) ?
            [] :
            {} :
          input[ index ]
    
        newValue = assocPathFn(
          Array.prototype.slice.call(pathArrValue, 1),
          newValue,
          nextinput
        )
      }
    
      if (_isInteger(index) && _isArray(input)){
        const arr = input.slice()
        arr[ index ] = newValue
    
        return arr
      }
    
      return assoc(
        index, newValue, input
      )
    }
    
    export const assocPath = curry(assocPathFn)
    Tests
    import { assocPath } from './assocPath'
    
    test('string can be used as path input', () => {
      const testObj = {
        a : [ { b : 1 }, { b : 2 } ],
        d : 3,
      }
      const result = assocPath(
        'a.0.b', 10, testObj
      )
      const expected = {
        a : [ { b : 10 }, { b : 2 } ],
        d : 3,
      }
      expect(result).toEqual(expected)
    })
    
    test('bug', () => {
      /*
        https://github.com/selfrefactor/rambda/issues/524
      */
      const state = {}
    
      const withDateLike = assocPath(
        [ 'outerProp', '2020-03-10' ],
        { prop : 2 },
        state
      )
      const withNumber = assocPath(
        [ 'outerProp', '5' ], { prop : 2 }, state
      )
    
      const withDateLikeExpected = { outerProp : { '2020-03-10' : { prop : 2 } } }
      const withNumberExpected = { outerProp : { 5 : { prop : 2 } } }
      expect(withDateLike).toEqual(withDateLikeExpected)
      expect(withNumber).toEqual(withNumberExpected)
    })
    
    test('adds a key to an empty object', () => {
      expect(assocPath(
        [ 'a' ], 1, {}
      )).toEqual({ a : 1 })
    })
    
    test('adds a key to a non-empty object', () => {
      expect(assocPath(
        'b', 2, { a : 1 }
      )).toEqual({
        a : 1,
        b : 2,
      })
    })
    
    test('adds a nested key to a non-empty object', () => {
      expect(assocPath(
        'b.c', 2, { a : 1 }
      )).toEqual({
        a : 1,
        b : { c : 2 },
      })
    })
    
    test('adds a nested key to a nested non-empty object - curry case 1', () => {
      expect(assocPath('b.d',
        3)({
        a : 1,
        b : { c : 2 },
      })).toEqual({
        a : 1,
        b : {
          c : 2,
          d : 3,
        },
      })
    })
    
    test('adds a key to a non-empty object - curry case 1', () => {
      expect(assocPath('b', 2)({ a : 1 })).toEqual({
        a : 1,
        b : 2,
      })
    })
    
    test('adds a nested key to a non-empty object - curry case 1', () => {
      expect(assocPath('b.c', 2)({ a : 1 })).toEqual({
        a : 1,
        b : { c : 2 },
      })
    })
    
    test('adds a key to a non-empty object - curry case 2', () => {
      expect(assocPath('b')(2, { a : 1 })).toEqual({
        a : 1,
        b : 2,
      })
    })
    
    test('adds a key to a non-empty object - curry case 3', () => {
      const result = assocPath('b')(2)({ a : 1 })
    
      expect(result).toEqual({
        a : 1,
        b : 2,
      })
    })
    
    test('changes an existing key', () => {
      expect(assocPath(
        'a', 2, { a : 1 }
      )).toEqual({ a : 2 })
    })
    
    test('undefined is considered an empty object', () => {
      expect(assocPath(
        'a', 1, undefined
      )).toEqual({ a : 1 })
    })
    
    test('null is considered an empty object', () => {
      expect(assocPath(
        'a', 1, null
      )).toEqual({ a : 1 })
    })
    
    test('value can be null', () => {
      expect(assocPath(
        'a', null, null
      )).toEqual({ a : null })
    })
    
    test('value can be undefined', () => {
      expect(assocPath(
        'a', undefined, null
      )).toEqual({ a : undefined })
    })
    
    test('assignment is shallow', () => {
      expect(assocPath(
        'a', { b : 2 }, { a : { c : 3 } }
      )).toEqual({ a : { b : 2 } })
    })
    
    test('empty array as path', () => {
      const result = assocPath(
        [], 3, {
          a : 1,
          b : 2,
        }
      )
      expect(result).toEqual(3)
    })
    
    test('happy', () => {
      const expected = { foo : { bar : { baz : 42 } } }
      const result = assocPath(
        [ 'foo', 'bar', 'baz' ], 42, { foo : null }
      )
      expect(result).toEqual(expected)
    })
    Typescript test
    import {assocPath} from 'rambda'
    
    interface Output {
      a: number,
      foo: {bar: number},
    }
    
    describe('R.assocPath - user must explicitly set type of output', () => {
      it('with array as path input', () => {
        const result = assocPath<Output>(['foo', 'bar'], 2, {a: 1})
    
        result // $ExpectType Output
      })
      it('with string as path input', () => {
        const result = assocPath<Output>('foo.bar', 2, {a: 1})
    
        result // $ExpectType Output
      })
    })
    
    describe('R.assocPath - curried', () => {
      it('with array as path input', () => {
        const result = assocPath<Output>(['foo', 'bar'], 2)({a: 1})
    
        result // $ExpectType Output
      })
      it('with string as path input', () => {
        const result = assocPath<Output>('foo.bar', 2)({a: 1})
    
        result // $ExpectType Output
      })
    })

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

    both

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

    It returns a function with input argument.

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

    All Typescript definitions
    both(pred1: Pred, pred2: Pred): Pred;
    both<T>(pred1: Predicate<T>, pred2: Predicate<T>): Predicate<T>;
    both<T>(pred1: Predicate<T>): (pred2: Predicate<T>) => Predicate<T>;
    both(pred1: Pred): (pred2: Pred) => Pred;
    R.both source
    export function both(f, g){
      if (arguments.length === 1) return _g => both(f, _g)
    
      return (...input) => f(...input) && g(...input)
    }
    Tests
    import { both } from './both'
    
    const firstFn = val => val > 0
    const secondFn = val => val < 10
    
    test('with curry', () => {
      expect(both(firstFn)(secondFn)(17)).toBeFalse()
    })
    
    test('without curry', () => {
      expect(both(firstFn, secondFn)(7)).toBeTrue()
    })
    
    test('with multiple inputs', () => {
      const between = function (
        a, b, c
      ){
        return a < b && b < c
      }
      const total20 = function (
        a, b, c
      ){
        return a + b + c === 20
      }
      const fn = both(between, total20)
      expect(fn(
        5, 7, 8
      )).toBeTrue()
    })
    
    test('skip evaluation of the second expression', () => {
      let effect = 'not evaluated'
      const F = function (){
        return false
      }
      const Z = function (){
        effect = 'Z got evaluated'
      }
      both(F, Z)()
    
      expect(effect).toBe('not evaluated')
    })
    Typescript test
    import {both} from 'rambda'
    
    describe('R.both', () => {
      it('with passed type', () => {
        const fn = both<number>(
          x => x > 1,
          x => x % 2 === 0
        )
        fn // $ExpectType Predicate<number>
        const result = fn(2) // $ExpectType boolean
        result // $ExpectType boolean
      })
      it('with passed type - curried', () => {
        const fn = both<number>(x => x > 1)(x => x % 2 === 0)
        fn // $ExpectType Predicate<number>
        const result = fn(2)
        result // $ExpectType boolean
      })
      it('no type passed', () => {
        const fn = both(
          x => {
            x // $ExpectType any
            return x > 1
          },
          x => {
            x // $ExpectType any
            return x % 2 === 0
          }
        )
        const result = fn(2)
        result // $ExpectType boolean
      })
      it('no type passed - curried', () => {
        const fn = both((x: number) => {
          x // $ExpectType number
          return x > 1
        })((x: number) => {
          x // $ExpectType number
          return x % 2 === 0
        })
        const result = fn(2)
        result // $ExpectType boolean
      })
    })

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

    chain

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

    The method is also known as flatMap.

    All Typescript definitions
    chain<T, U>(fn: (n: T) => U[], list: T[]): U[];
    chain<T, U>(fn: (n: T) => U[]): (list: T[]) => U[];
    chain<X0, X1, R>(fn: (x0: X0, x1: X1) => R, fn1: (x1: X1) => X0): (x1: X1) => R;
    R.chain source
    export function chain(fn, list){
      if (arguments.length === 1){
        return _list => chain(fn, _list)
      }
    
      return [].concat(...list.map(fn))
    }
    Tests
    import { chain } from './chain'
    
    const duplicate = n => [ n, n ]
    
    test('happy', () => {
      const fn = x => [ x * 2 ]
      const list = [ 1, 2, 3 ]
    
      const result = chain(fn, list)
    
      expect(result).toEqual([ 2, 4, 6 ])
    })
    
    test('maps then flattens one level', () => {
      expect(chain(duplicate, [ 1, 2, 3 ])).toEqual([ 1, 1, 2, 2, 3, 3 ])
    })
    
    test('maps then flattens one level - curry', () => {
      expect(chain(duplicate)([ 1, 2, 3 ])).toEqual([ 1, 1, 2, 2, 3, 3 ])
    })
    
    test('flattens only one level', () => {
      const nest = n => [ [ n ] ]
      expect(chain(nest, [ 1, 2, 3 ])).toEqual([ [ 1 ], [ 2 ], [ 3 ] ])
    })
    Typescript test
    import {chain} from 'rambda'
    
    const list = [1, 2, 3]
    const fn = (x: number) => [`${x}`, `${x}`]
    
    describe('R.chain', () => {
      it('without passing type', () => {
        const result = chain(fn, list)
        result // $ExpectType string[]
    
        const curriedResult = chain(fn)(list)
        curriedResult // $ExpectType string[]
      })
    })

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

    clamp

    clamp(min: number, max: number, input: number): number

    Restrict a number input to be within min and max limits.

    If input is bigger than max, then the result is max.

    If input is smaller than min, then the result is min.

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

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

    clone

    clone<T>(input: T): T

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

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

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

    complement

    complement<T extends any[]>(pred: (...args: T) => boolean): (...args: T) => boolean

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

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

    All Typescript definitions
    complement<T extends any[]>(pred: (...args: T) => boolean): (...args: T) => boolean;
    R.complement source
    export function complement(fn){
      return (...input) => !fn(...input)
    }
    Tests
    import { complement } from './complement'
    
    test('happy', () => {
      const fn = complement(x => x.length === 0)
    
      expect(fn([ 1, 2, 3 ])).toBeTrue()
    })
    
    test('with multiple parameters', () => {
      const between = function (
        a, b, c
      ){
        return a < b && b < c
      }
      const f = complement(between)
      expect(f(
        4, 5, 11
      )).toEqual(false)
      expect(f(
        12, 2, 6
      )).toEqual(true)
    })
    Typescript test
    import {complement, isNil} from 'rambda'
    
    describe('R.complement', () => {
      it('happy', () => {
        const fn = complement(isNil)
        const result = fn(null)
        result // $ExpectType boolean
      })
    })

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

    compose

    It performs right-to-left function composition.

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

    concat

    concat<T>(x: T[], y: T[]): T[]

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

    All Typescript definitions
    concat<T>(x: T[], y: T[]): T[];
    concat<T>(x: T[]): (y: T[]) => T[];
    concat(x: string, y: string): string;
    concat(x: string): (y: string) => string;
    R.concat source
    export function concat(x, y){
      if (arguments.length === 1) return _y => concat(x, _y)
    
      return typeof x === 'string' ? `${ x }${ y }` : [ ...x, ...y ]
    }
    Tests
    import { concat } from './concat'
    
    test('happy', () => {
      const arr1 = [ 'a', 'b', 'c' ]
      const arr2 = [ 'd', 'e', 'f' ]
    
      const a = concat(arr1, arr2)
      const b = concat(arr1)(arr2)
      const expectedResult = [ 'a', 'b', 'c', 'd', 'e', 'f' ]
    
      expect(a).toEqual(expectedResult)
      expect(b).toEqual(expectedResult)
    })
    
    test('with strings', () => {
      expect(concat('ABC', 'DEF')).toEqual('ABCDEF')
    })
    Typescript test
    import {concat} from 'rambda'
    
    const list1 = [1, 2, 3]
    const list2 = [4, 5, 6]
    
    describe('R.concat', () => {
      it('happy', () => {
        const result = concat(list1, list2)
    
        result // $ExpectType number[]
      })
      it('curried', () => {
        const result = concat(list1)(list2)
    
        result // $ExpectType number[]
      })
    })

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

    cond

    cond(conditions: ([Pred, (...a: any[]) => any])[]): (...x: any[]) => any

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

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

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

    If no winner is found, then fn returns undefined.

    All Typescript definitions
    cond(conditions: ([Pred, (...a: any[]) => any])[]): (...x: any[]) => any;
    cond<A, B>(conditions: ([SafePred<A>, (...a: A[]) => B])[]): (...x: A[]) => B;
    R.cond source
    export function cond(conditions){
      return input => {
        let done = false
        let toReturn
        conditions.forEach(([ predicate, resultClosure ]) => {
          if (!done && predicate(input)){
            done = true
            toReturn = resultClosure(input)
          }
        })
    
        return toReturn
      }
    }
    Tests
    import { always } from './always'
    import { cond } from './cond'
    import { equals } from './equals'
    import { T } from './T'
    
    test('returns a function', () => {
      expect(typeof cond([])).toEqual('function')
    })
    
    test('returns a conditional function', () => {
      const fn = cond([
        [ equals(0), always('water freezes at 0°C') ],
        [ equals(100), always('water boils at 100°C') ],
        [
          T,
          function (temp){
            return 'nothing special happens at ' + temp + '°C'
          },
        ],
      ])
      expect(fn(0)).toEqual('water freezes at 0°C')
      expect(fn(50)).toEqual('nothing special happens at 50°C')
      expect(fn(100)).toEqual('water boils at 100°C')
    })
    
    test('no winner', () => {
      const fn = cond([
        [ equals('foo'), always(1) ],
        [ equals('bar'), always(2) ],
      ])
      expect(fn('quux')).toEqual(undefined)
    })
    
    test('predicates are tested in order', () => {
      const fn = cond([
        [ T, always('foo') ],
        [ T, always('bar') ],
        [ T, always('baz') ],
      ])
      expect(fn()).toEqual('foo')
    })
    Typescript test
    import {cond, always, equals} from 'rambda'
    
    describe('R.cond', () => {
      it('happy', () => {
        const fn = cond<number, string>([
          [equals(0), always('water freezes at 0°C')],
          [equals(100), always('water boils at 100°C')],
          [
            () => true,
            function(temp) {
              return 'nothing special happens at ' + temp + '°C'
            },
          ],
        ])
    
        const result = fn(0)
        result // $ExpectType string
      })
    })

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

    converge

    converge(after: ((...a: any[]) => any), fns: ((...x: any[]) => any)[]): (...y: any[]) => any

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

    All Typescript definitions
    converge(after: ((...a: any[]) => any), fns: ((...x: any[]) => any)[]): (...y: any[]) => any;
    R.converge source
    import { curryN } from './curryN'
    import { map } from './map'
    import { max } from './max'
    import { reduce } from './reduce'
    
    export function converge(fn, transformers){
      if (arguments.length === 1)
        return _transformers => converge(fn, _transformers)
    
      const highestArity = reduce(
        (a, b) => max(a, b.length), 0, transformers
      )
    
      return curryN(highestArity, function (){
        return fn.apply(this,
          map(g => g.apply(this, arguments), transformers))
      })
    }
    Tests
    import { add } from './add'
    import { converge } from './converge'
    import { multiply } from './multiply'
    
    const f1 = converge(multiply, [ a => a + 1, a => a + 10 ])
    const f2 = converge(multiply, [ a => a + 1, (a, b) => a + b + 10 ])
    const f3 = converge(multiply, [ a => a + 1, (
      a, b, c
    ) => a + b + c + 10 ])
    
    test('happy', () => {
      expect(f2(6, 7)).toEqual(161)
    })
    
    test('passes the results of applying the arguments individually', () => {
      const result = converge(multiply)([ add(1), add(3) ])(2)
      expect(result).toEqual(15)
    })
    
    test('returns a function with the length of the longest argument', () => {
      expect(f1.length).toEqual(1)
      expect(f2.length).toEqual(2)
      expect(f3.length).toEqual(3)
    })
    
    test('passes context to its functions', () => {
      const a = function (x){
        return this.f1(x)
      }
      const b = function (x){
        return this.f2(x)
      }
      const c = function (x, y){
        return this.f3(x, y)
      }
      const d = converge(c, [ a, b ])
      const context = {
        f1 : add(1),
        f2 : add(2),
        f3 : add,
      }
      expect(a.call(context, 1)).toEqual(2)
      expect(b.call(context, 1)).toEqual(3)
      expect(d.call(context, 1)).toEqual(5)
    })
    
    test('works with empty functions list', () => {
      const fn = converge(function (){
        return arguments.length
      }, [])
      expect(fn.length).toEqual(0)
      expect(fn()).toEqual(0)
    })
    Typescript test
    import {converge} from 'ramda'
    
    const mult = (a: number, b: number) => {
      return a * b
    }
    const fn = converge(mult, [
      (a: number) => {
        return a
      },
      (a: number, b: number) => {
        return b
      },
    ])
    
    describe('R.converge', () => {
      it('happy', () => {
        const result = fn(2, 3)
        const curriedResult = fn(2)(3)
    
        result // $ExpectType any
        curriedResult // $ExpectType any
      })
    })
    Rambda is slower than Ramda with 78.63%
    const R = require('../../dist/rambda.js')
    
    const converge = [
      {
        label : 'Rambda',
        fn    : () => {
          const fn = Ramda.converge(Ramda.multiply, [ Ramda.add(1), Ramda.add(3) ])
    
          fn(4)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          const fn = R.converge(R.multiply, [ R.add(1), R.add(3) ])
    
          fn(4)
        },
      },
    ]
    

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

    curry

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

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

    curryN

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

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

    dec

    It decrements a number.

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

    defaultTo

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

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

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

    All Typescript definitions
    defaultTo<T>(defaultValue: T, input: T | null | undefined): T;
    defaultTo<T>(defaultValue: T): (input: T | null | undefined) => T;
    R.defaultTo source
    function isFalsy(input){
      return (
        input === undefined ||
        input === null ||
        Number.isNaN(input) === true
      )
    }
    
    export function defaultTo(defaultArgument, input){
      if (arguments.length === 1){
        return _input =>
          defaultTo(defaultArgument, _input)
      }
    
      return isFalsy(input) ? defaultArgument : input
    }
    Tests
    import { defaultTo } from './defaultTo'
    
    test('with undefined', () => {
      expect(defaultTo('foo')(undefined)).toEqual('foo')
    })
    
    test('with null', () => {
      expect(defaultTo('foo')(null)).toEqual('foo')
    })
    
    test('with NaN', () => {
      expect(defaultTo('foo')(NaN)).toEqual('foo')
    })
    
    test('with empty string', () => {
      expect(defaultTo('foo', '')).toEqual('')
    })
    
    test('with false', () => {
      expect(defaultTo('foo', false)).toEqual(false)
    })
    
    test('when inputArgument passes initial check', () => {
      expect(defaultTo('foo', 'bar')).toEqual('bar')
    })
    Typescript test
    import {defaultTo} from 'rambda'
    
    describe('R.defaultTo with Ramda spec', () => {
      it('happy', () => {
        const result = defaultTo('foo', '')
        result // $ExpectType "" | "foo"
      })
      it('with explicit type', () => {
        const result = defaultTo<string>('foo', null)
        result // $ExpectType string
      })
    })
    Rambda is faster than Ramda with 48.91%
    const R = require('../../dist/rambda.js')
    
    const input = [ null, undefined, 5 ]
    
    const defaultTo = [
      {
        label : 'Rambda',
        fn    : () => {
          R.defaultTo(3, input[ 0 ])
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.defaultTo(3, input[ 0 ])
        },
      },
      {
        label : 'Rambda with multiple arguments',
        fn    : () => {
          R.defaultTo(3, ...input)
        },
      },
    ]
    

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

    difference

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

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

    R.equals is used to determine equality.

    All Typescript definitions
    difference<T>(a: T[], b: T[]): T[];
    difference<T>(a: T[]): (b: T[]) => T[];
    R.difference source
    import { includes } from './includes'
    import { uniq } from './uniq'
    
    export function difference(a, b){
      if (arguments.length === 1) return _b => difference(a, _b)
    
      return uniq(a).filter(aInstance => !includes(aInstance, b))
    }
    Tests
    import { difference } from './difference'
    import { difference as differenceRamda } from 'ramda'
    
    test('difference', () => {
      const a = [ 1, 2, 3, 4 ]
      const b = [ 3, 4, 5, 6 ]
      expect(difference(a)(b)).toEqual([ 1, 2 ])
    
      expect(difference([], [])).toEqual([])
    })
    
    test('difference with objects', () => {
      const a = [ { id : 1 }, { id : 2 }, { id : 3 }, { id : 4 } ]
      const b = [ { id : 3 }, { id : 4 }, { id : 5 }, { id : 6 } ]
      expect(difference(a, b)).toEqual([ { id : 1 }, { id : 2 } ])
    })
    
    test('no duplicates in first list', () => {
      const M2 = [ 1, 2, 3, 4, 1, 2, 3, 4 ]
      const N2 = [ 3, 3, 4, 4, 5, 5, 6, 6 ]
      expect(difference(M2, N2)).toEqual([ 1, 2 ])
    })
    
    test('should use R.equals', () => {
      expect(difference([ 1 ], [ 1 ]).length).toEqual(0)
      expect(differenceRamda([ NaN ], [ NaN ]).length).toEqual(0)
    })
    Typescript test
    import {difference} from 'rambda'
    
    const list1 = [1, 2, 3]
    const list2 = [1, 2, 4]
    
    describe('R.difference', () => {
      it('happy', () => {
        const result = difference(list1, list2)
    
        result // $ExpectType number[]
      })
      it('curried', () => {
        const result = difference(list1)(list2)
    
        result // $ExpectType number[]
      })
    })

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

    dissoc

    It returns a new object that does not contain property prop.

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

    divide

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

    drop

    drop<T>(howMany: number, input: T[]): T[]

    It returns howMany items dropped from beginning of list or string input.

    All Typescript definitions
    drop<T>(howMany: number, input: T[]): T[];
    drop(howMany: number, input: string): string;
    drop<T>(howMany: number): {
      <T>(input: T[]): T[];
      (input: string): string;
    };
    R.drop source
    export function drop(howManyToDrop, listOrString){
      if (arguments.length === 1) return _list => drop(howManyToDrop, _list)
    
      return listOrString.slice(howManyToDrop > 0 ? howManyToDrop : 0)
    }
    Tests
    import assert from 'assert'
    
    import { drop } from './drop'
    
    test('with array', () => {
      expect(drop(2)([ 'foo', 'bar', 'baz' ])).toEqual([ 'baz' ])
      expect(drop(3, [ 'foo', 'bar', 'baz' ])).toEqual([])
      expect(drop(4, [ 'foo', 'bar', 'baz' ])).toEqual([])
    })
    
    test('with string', () => {
      expect(drop(3, 'rambda')).toEqual('bda')
    })
    
    test('with non-positive count', () => {
      expect(drop(0, [ 1, 2, 3 ])).toEqual([ 1, 2, 3 ])
      expect(drop(-1, [ 1, 2, 3 ])).toEqual([ 1, 2, 3 ])
      expect(drop(-Infinity, [ 1, 2, 3 ])).toEqual([ 1, 2, 3 ])
    })
    
    test('should return copy', () => {
      const xs = [ 1, 2, 3 ]
    
      assert.notStrictEqual(drop(0, xs), xs)
      assert.notStrictEqual(drop(-1, xs), xs)
    })
    Typescript test
    import {drop} from 'rambda'
    
    const list = [1, 2, 3, 4]
    const str = 'foobar'
    const howMany = 2
    
    describe('R.drop - array', () => {
      it('happy', () => {
        const result = drop(howMany, list)
        result // $ExpectType number[]
      })
      it('curried', () => {
        const result = drop(howMany)(list)
        result // $ExpectType number[]
      })
    })
    
    describe('R.drop - string', () => {
      it('happy', () => {
        const result = drop(howMany, str)
        result // $ExpectType string
      })
      it('curried', () => {
        const result = drop(howMany)(str)
        result // $ExpectType string
      })
    })
    Rambda is faster than Ramda with 82.35%
    const R = require('../../dist/rambda.js')
    
    const input = [ 1, 2, 3, 4 ]
    
    const drop = [
      {
        label : 'Rambda',
        fn    : () => {
          R.drop(3, input)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.drop(3, input)
        },
      },
    ]
    

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

    dropLast

    dropLast<T>(howMany: number, input: T[]): T[]

    It returns howMany items dropped from the end of list or string input.

    All Typescript definitions
    dropLast<T>(howMany: number, input: T[]): T[];
    dropLast(howMany: number, input: string): string;
    dropLast<T>(howMany: number): {
      <T>(input: T[]): T[];
      (input: string): string;
    };
    R.dropLast source
    export function dropLast(howManyToDrop, listOrString){
      if (arguments.length === 1){
        return _listOrString => dropLast(howManyToDrop, _listOrString)
      }
    
      return howManyToDrop > 0 ?
        listOrString.slice(0, -howManyToDrop) :
        listOrString.slice()
    }
    Tests
    import assert from 'assert'
    
    import { dropLast } from './dropLast'
    
    test('with array', () => {
      expect(dropLast(2)([ 'foo', 'bar', 'baz' ])).toEqual([ 'foo' ])
      expect(dropLast(3, [ 'foo', 'bar', 'baz' ])).toEqual([])
      expect(dropLast(4, [ 'foo', 'bar', 'baz' ])).toEqual([])
    })
    
    test('with string', () => {
      expect(dropLast(3, 'rambda')).toEqual('ram')
    })
    
    test('with non-positive count', () => {
      expect(dropLast(0, [ 1, 2, 3 ])).toEqual([ 1, 2, 3 ])
      expect(dropLast(-1, [ 1, 2, 3 ])).toEqual([ 1, 2, 3 ])
      expect(dropLast(-Infinity, [ 1, 2, 3 ])).toEqual([ 1, 2, 3 ])
    })
    
    test('should return copy', () => {
      const xs = [ 1, 2, 3 ]
    
      assert.notStrictEqual(dropLast(0, xs), xs)
      assert.notStrictEqual(dropLast(-1, xs), xs)
    })
    Typescript test
    import {dropLast} from 'rambda'
    
    const list = [1, 2, 3, 4]
    const str = 'foobar'
    const howMany = 2
    
    describe('R.dropLast - array', () => {
      it('happy', () => {
        const result = dropLast(howMany, list)
        result // $ExpectType number[]
      })
      it('curried', () => {
        const result = dropLast(howMany)(list)
        result // $ExpectType number[]
      })
    })
    
    describe('R.dropLast - string', () => {
      it('happy', () => {
        const result = dropLast(howMany, str)
        result // $ExpectType string
      })
      it('curried', () => {
        const result = dropLast(howMany)(str)
        result // $ExpectType string
      })
    })
    Rambda is faster than Ramda with 86.74%
    const R = require('../../dist/rambda.js')
    
    const input = [ 1, 2, 3, 4 ]
    
    const dropLast = [
      {
        label : 'Rambda',
        fn    : () => {
          R.dropLast(3, input)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.dropLast(3, input)
        },
      },
    ]
    

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

    dropLastWhile

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

    dropRepeats

    dropRepeats<T>(list: T[]): T[]

    It removes any successive duplicates according to R.equals.

    All Typescript definitions
    dropRepeats<T>(list: T[]): T[];
    R.dropRepeats source
    import { _isArray } from './_internals/_isArray'
    import { equals } from './equals'
    
    export function dropRepeats(list){
      if (!_isArray(list)){
        throw new Error(`${ list } is not a list`)
      }
    
      const toReturn = []
    
      list.reduce((prev, current) => {
        if (!equals(prev, current)){
          toReturn.push(current)
        }
    
        return current
      }, undefined)
    
      return toReturn
    }
    Tests
    import { dropRepeats as dropRepeatsRamda } from 'ramda'
    
    import { compareCombinations } from './_internals/testUtils'
    import { add } from './add'
    import { dropRepeats } from './dropRepeats'
    
    const list = [ 1, 2, 2, 2, 3, 4, 4, 5, 5, 3, 2, 2, { a : 1 }, { a : 1 } ]
    const listClean = [ 1, 2, 3, 4, 5, 3, 2, { a : 1 } ]
    
    test('happy', () => {
      const result = dropRepeats(list)
      expect(result).toEqual(listClean)
    })
    
    const possibleLists = [
      [ add(1), async () => {}, [ 1 ], [ 1 ], [ 2 ], [ 2 ] ],
      [ add(1), add(1), add(2) ],
      [],
      1,
      /foo/g,
      Promise.resolve(1),
    ]
    
    describe('brute force', () => {
      compareCombinations({
        firstInput : possibleLists,
        callback   : errorsCounters => {
          expect(errorsCounters).toMatchInlineSnapshot(`
            Object {
              "ERRORS_MESSAGE_MISMATCH": 0,
              "ERRORS_TYPE_MISMATCH": 0,
              "RESULTS_MISMATCH": 1,
              "SHOULD_NOT_THROW": 3,
              "SHOULD_THROW": 0,
            }
          `)
        },
        fn      : dropRepeats,
        fnRamda : dropRepeatsRamda,
      })
    })
    Typescript test
    import {dropRepeats} from 'rambda'
    
    describe('R.dropRepeats', () => {
      it('happy', () => {
        const result = dropRepeats([1, 2, 2, 3])
    
        result // $ExpectType number[]
      })
    })

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

    dropRepeatsWith

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

    dropWhile

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

    either

    either(firstPredicate: Pred, secondPredicate: Pred): Pred

    It returns a new predicate function from firstPredicate and secondPredicate inputs.

    This predicate function will return true, if any of the two input predicates return true.

    All Typescript definitions
    either(firstPredicate: Pred, secondPredicate: Pred): Pred;
    either<T>(firstPredicate: Predicate<T>, secondPredicate: Predicate<T>): Predicate<T>;
    either<T>(firstPredicate: Predicate<T>): (secondPredicate: Predicate<T>) => Predicate<T>;
    either(firstPredicate: Pred): (secondPredicate: Pred) => Pred;
    R.either source
    export function either(firstPredicate, secondPredicate){
      if (arguments.length === 1){
        return _secondPredicate => either(firstPredicate, _secondPredicate)
      }
    
      return (...input) =>
        Boolean(firstPredicate(...input) || secondPredicate(...input))
    }
    Tests
    import { either } from './either'
    
    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 = either(between, total20)
      expect(fn(
        7, 8, 5
      )).toBeTrue()
    })
    
    test('skip evaluation of the second expression', () => {
      let effect = 'not evaluated'
      const F = function (){
        return true
      }
      const Z = function (){
        effect = 'Z got evaluated'
      }
      either(F, Z)()
    
      expect(effect).toBe('not evaluated')
    })
    
    test('case 1', () => {
      const firstFn = val => val > 0
      const secondFn = val => val * 5 > 10
    
      expect(either(firstFn, secondFn)(1)).toBeTrue()
    })
    
    test('case 2', () => {
      const firstFn = val => val > 0
      const secondFn = val => val === -10
      const fn = either(firstFn)(secondFn)
    
      expect(fn(-10)).toBeTrue()
    })
    Typescript test
    import {either} from 'rambda'
    
    describe('R.either', () => {
      it('with passed type', () => {
        const fn = either<number>(
          x => x > 1,
          x => x % 2 === 0
        )
        fn // $ExpectType Predicate<number>
        const result = fn(2) // $ExpectType boolean
        result // $ExpectType boolean
      })
      it('with passed type - curried', () => {
        const fn = either<number>(x => x > 1)(x => x % 2 === 0)
        fn // $ExpectType Predicate<number>
        const result = fn(2)
        result // $ExpectType boolean
      })
      it('no type passed', () => {
        const fn = either(
          x => {
            x // $ExpectType any
            return x > 1
          },
          x => {
            x // $ExpectType any
            return x % 2 === 0
          }
        )
        const result = fn(2)
        result // $ExpectType boolean
      })
      it('no type passed - curried', () => {
        const fn = either((x: number) => {
          x // $ExpectType number
          return x > 1
        })((x: number) => {
          x // $ExpectType number
          return x % 2 === 0
        })
        const result = fn(2)
        result // $ExpectType boolean
      })
    })

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

    endsWith

    endsWith(target: string, str: string): boolean

    Curried version of String.prototype.endsWith

    All Typescript definitions
    endsWith(target: string, str: string): boolean;
    endsWith(target: string): (str: string) => boolean;
    R.endsWith source
    export function endsWith(target, str){
      if (arguments.length === 1) return _str => endsWith(target, _str)
    
      return str.endsWith(target)
    }
    Tests
    import { endsWith } from './endsWith'
    
    test('happy', () => {
      expect(endsWith('bar', 'foo-bar')).toBeTrue()
      expect(endsWith('baz')('foo-bar')).toBeFalse()
    })
    
    test('does not work with arrays', () => {
      expect(() => endsWith([ 'c' ], [ 'a', 'b', 'c' ])).toThrowWithMessage(Error,
        'str.endsWith is not a function')
    })
    Typescript test
    import {endsWith} from 'rambda'
    
    const target = 'foo'
    const input = 'foo bar'
    
    describe('R.endsWith', () => {
      it('happy', () => {
        const result = endsWith(target, input)
    
        result // $ExpectType boolean
      })
      it('curried', () => {
        const result = endsWith(target)(input)
    
        result // $ExpectType boolean
      })
    })

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

    eqProps

    It returns true if property prop in obj1 is equal to property prop in obj2 according to R.equals.

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

    equals

    equals<T>(x: T, y: T): boolean

    It deeply compares x and y and returns true if they are equal.

    All Typescript definitions
    equals<T>(x: T, y: T): boolean;
    equals<T>(x: T): (y: T) => boolean;
    R.equals source
    import { type } from './type'
    
    function parseError(maybeError){
      const typeofError = maybeError.__proto__.toString()
      if (![ 'Error', 'TypeError' ].includes(typeofError)) return []
    
      return [ typeofError, maybeError.message ]
    }
    
    function parseDate(maybeDate){
      if (!maybeDate.toDateString) return [ false ]
    
      return [ true, maybeDate.getTime() ]
    }
    
    function parseRegex(maybeRegex){
      if (maybeRegex.constructor !== RegExp) return [ false ]
    
      return [ true, maybeRegex.toString() ]
    }
    
    export function equals(a, b){
      if (arguments.length === 1) return _b => equals(a, _b)
    
      const aType = type(a)
      if (aType !== type(b)) return false
      if (aType === 'Function'){
        return a.name === undefined ? false : a.name === b.name
      }
    
      if ([ 'NaN', 'Undefined', 'Null' ].includes(aType)) return true
    
      if (aType === 'Number'){
        if (Object.is(-0, a) !== Object.is(-0, b)) return false
    
        return a.toString() === b.toString()
      }
    
      if ([ 'String', 'Boolean' ].includes(aType)){
        return a.toString() === b.toString()
      }
    
      if (aType === 'Array'){
        const aClone = Array.from(a)
        const bClone = Array.from(b)
    
        if (aClone.toString() !== bClone.toString()){
          return false
        }
    
        let loopArrayFlag = true
        aClone.forEach((aCloneInstance, aCloneIndex) => {
          if (loopArrayFlag){
            if (
              aCloneInstance !== bClone[ aCloneIndex ] &&
              !equals(aCloneInstance, bClone[ aCloneIndex ])
            ){
              loopArrayFlag = false
            }
          }
        })
    
        return loopArrayFlag
      }
    
      const aRegex = parseRegex(a)
      const bRegex = parseRegex(b)
    
      if (aRegex[ 0 ]){
        return bRegex[ 0 ] ? aRegex[ 1 ] === bRegex[ 1 ] : false
      } else if (bRegex[ 0 ]) return false
    
      const aDate = parseDate(a)
      const bDate = parseDate(b)
    
      if (aDate[ 0 ]){
        return bDate[ 0 ] ? aDate[ 1 ] === bDate[ 1 ] : false
      } else if (bDate[ 0 ]) return false
    
      const aError = parseError(a)
      const bError = parseError(b)
    
      if (aError[ 0 ]){
        return bError[ 0 ] ?
          aError[ 0 ] === bError[ 0 ] && aError[ 1 ] === bError[ 1 ] :
          false
      }
    
      if (aType === 'Object'){
        const aKeys = Object.keys(a)
    
        if (aKeys.length !== Object.keys(b).length){
          return false
        }
    
        let loopObjectFlag = true
        aKeys.forEach(aKeyInstance => {
          if (loopObjectFlag){
            const aValue = a[ aKeyInstance ]
            const bValue = b[ aKeyInstance ]
    
            if (aValue !== bValue && !equals(aValue, bValue)){
              loopObjectFlag = false
            }
          }
        })
    
        return loopObjectFlag
      }
    
      return false
    }
    Tests
    import { equals } from './equals'
    
    test('compare functions', () => {
      function foo(){}
      function bar(){}
      const baz = () => {}
    
      const expectTrue = equals(foo, foo)
      const expectFalseFirst = equals(foo, bar)
      const expectFalseSecond = equals(foo, baz)
    
      expect(expectTrue).toBeTrue()
      expect(expectFalseFirst).toBeFalse()
      expect(expectFalseSecond).toBeFalse()
    })
    
    test('with array of objects', () => {
      const list1 = [ { a : 1 }, [ { b : 2 } ] ]
      const list2 = [ { a : 1 }, [ { b : 2 } ] ]
      const list3 = [ { a : 1 }, [ { b : 3 } ] ]
    
      expect(equals(list1, list2)).toBeTrue()
      expect(equals(list1, list3)).toBeFalse()
    })
    
    test('with regex', () => {
      expect(equals(/s/, /s/)).toEqual(true)
      expect(equals(/s/, /d/)).toEqual(false)
      expect(equals(/a/gi, /a/gi)).toEqual(true)
      expect(equals(/a/gim, /a/gim)).toEqual(true)
      expect(equals(/a/gi, /a/i)).toEqual(false)
    })
    
    test('not a number', () => {
      expect(equals([ NaN ], [ NaN ])).toBeTrue()
    })
    
    test('new number', () => {
      expect(equals(new Number(0), new Number(0))).toEqual(true)
      expect(equals(new Number(0), new Number(1))).toEqual(false)
      expect(equals(new Number(1), new Number(0))).toEqual(false)
    })
    
    test('new string', () => {
      expect(equals(new String(''), new String(''))).toEqual(true)
      expect(equals(new String(''), new String('x'))).toEqual(false)
      expect(equals(new String('x'), new String(''))).toEqual(false)
      expect(equals(new String('foo'), new String('foo'))).toEqual(true)
      expect(equals(new String('foo'), new String('bar'))).toEqual(false)
      expect(equals(new String('bar'), new String('foo'))).toEqual(false)
    })
    
    test('new Boolean', () => {
      expect(equals(new Boolean(true), new Boolean(true))).toEqual(true)
      expect(equals(new Boolean(false), new Boolean(false))).toEqual(true)
      expect(equals(new Boolean(true), new Boolean(false))).toEqual(false)
      expect(equals(new Boolean(false), new Boolean(true))).toEqual(false)
    })
    
    test('new Error', () => {
      expect(equals(new Error('XXX'), {})).toEqual(false)
      expect(equals(new Error('XXX'), new TypeError('XXX'))).toEqual(false)
      expect(equals(new Error('XXX'), new Error('YYY'))).toEqual(false)
      expect(equals(new Error('XXX'), new Error('XXX'))).toEqual(true)
      expect(equals(new Error('XXX'), new TypeError('YYY'))).toEqual(false)
    })
    
    test('with dates', () => {
      expect(equals(new Date(0), new Date(0))).toEqual(true)
      expect(equals(new Date(1), new Date(1))).toEqual(true)
      expect(equals(new Date(0), new Date(1))).toEqual(false)
      expect(equals(new Date(1), new Date(0))).toEqual(false)
      expect(equals(new Date(0), {})).toEqual(false)
      expect(equals({}, new Date(0))).toEqual(false)
    })
    
    test('ramda spec', () => {
      expect(equals({}, {})).toEqual(true)
    
      expect(equals({
        a : 1,
        b : 2,
      },
      {
        a : 1,
        b : 2,
      })).toEqual(true)
    
      expect(equals({
        a : 2,
        b : 3,
      },
      {
        b : 3,
        a : 2,
      })).toEqual(true)
    
      expect(equals({
        a : 2,
        b : 3,
      },
      {
        a : 3,
        b : 3,
      })).toEqual(false)
    
      expect(equals({
        a : 2,
        b : 3,
        c : 1,
      },
      {
        a : 2,
        b : 3,
      })).toEqual(false)
    })
    
    test('works with boolean tuple', () => {
      expect(equals([ true, false ], [ true, false ])).toBeTrue()
      expect(equals([ true, false ], [ true, true ])).toBeFalse()
    })
    
    test('works with equal objects within array', () => {
      const objFirst = {
        a : {
          b : 1,
          c : 2,
          d : [ 1 ],
        },
      }
      const objSecond = {
        a : {
          b : 1,
          c : 2,
          d : [ 1 ],
        },
      }
    
      const x = [ 1, 2, objFirst, null, '', [] ]
      const y = [ 1, 2, objSecond, null, '', [] ]
      expect(equals(x, y)).toBeTrue()
    })
    
    test('works with different objects within array', () => {
      const objFirst = { a : { b : 1 } }
      const objSecond = { a : { b : 2 } }
    
      const x = [ 1, 2, objFirst, null, '', [] ]
      const y = [ 1, 2, objSecond, null, '', [] ]
      expect(equals(x, y)).toBeFalse()
    })
    
    test('works with undefined as second argument', () => {
      expect(equals(1, undefined)).toBeFalse()
    
      expect(equals(undefined, undefined)).toBeTrue()
    })
    
    test('various examples', () => {
      expect(equals([ 1, 2, 3 ])([ 1, 2, 3 ])).toBeTrue()
    
      expect(equals([ 1, 2, 3 ], [ 1, 2 ])).toBeFalse()
    
      expect(equals(1, 1)).toBeTrue()
    
      expect(equals(1, '1')).toBeFalse()
    
      expect(equals({}, {})).toBeTrue()
    
      expect(equals({
        a : 1,
        b : 2,
      },
      {
        b : 2,
        a : 1,
      })).toBeTrue()
    
      expect(equals({
        a : 1,
        b : 2,
      },
      {
        a : 1,
        b : 1,
      })).toBeFalse()
    
      expect(equals({
        a : 1,
        b : false,
      },
      {
        a : 1,
        b : 1,
      })).toBeFalse()
    
      expect(equals({
        a : 1,
        b : 2,
      },
      {
        b : 2,
        a : 1,
        c : 3,
      })).toBeFalse()
    
      expect(equals({
        x : {
          a : 1,
          b : 2,
        },
      },
      {
        x : {
          b : 2,
          a : 1,
          c : 3,
        },
      })).toBeFalse()
    
      expect(equals({
        a : 1,
        b : 2,
      },
      {
        b : 3,
        a : 1,
      })).toBeFalse()
    
      expect(equals({ a : { b : { c : 1 } } }, { a : { b : { c : 1 } } })).toBeTrue()
    
      expect(equals({ a : { b : { c : 1 } } }, { a : { b : { c : 2 } } })).toBeFalse()
    
      expect(equals({ a : {} }, { a : {} })).toBeTrue()
    
      expect(equals('', '')).toBeTrue()
    
      expect(equals('foo', 'foo')).toBeTrue()
    
      expect(equals('foo', 'bar')).toBeFalse()
    
      expect(equals(0, false)).toBeFalse()
    
      expect(equals(/\s/g, null)).toBeFalse()
    
      expect(equals(null, null)).toBeTrue()
    
      expect(equals(false)(null)).toBeFalse()
    })
    
    test('with custom functions', () => {
      function foo(){
        return 1
      }
      foo.prototype.toString = () => ''
      const result = equals(foo, foo)
    
      expect(result).toBeTrue()
    })
    
    test('with classes', () => {
      class Foo{}
      const foo = new Foo()
      const result = equals(foo, foo)
    
      expect(result).toBeTrue()
    })
    
    test('with negative zero', () => {
      expect(equals(-0, -0)).toBeTrue()
      expect(equals(-0, 0)).toBeFalse()
      expect(equals(0, 0)).toBeTrue()
      expect(equals(-0, 1)).toBeFalse()
    })
    Typescript test
    import {equals} from 'rambda'
    
    describe('R.equals', () => {
      it('happy', () => {
        const result = equals(4, 1)
        result // $ExpectType boolean
      })
      it('with object', () => {
        const foo = {a: 1}
        const bar = {a: 2}
        const result = equals(foo, bar)
        result // $ExpectType boolean
      })
      it('curried', () => {
        const result = equals(4)(1)
    
        result // $ExpectType boolean
      })
    })
    Lodash is fastest. Rambda is 58.37% slower and Ramda is 96.73% slower
    const R = require('../../dist/rambda.js')
    
    const mode = 0
    const limit = 10000
    
    const strings = Array(limit).fill(null).map(() => Math.floor(Math.random() * 1000))
    
    const modes = [
      strings
    ]
    const activeMode = modes[mode]
    
    const equals = [
      {
        label : 'Rambda',
        fn    : () => {
          activeMode.forEach(x => R.equals(x,'ss' ))
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          activeMode.forEach(x => Ramda.equals(x,'ss' ))
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          activeMode.forEach(x => _.isEqual(x,'ss' ))
        },
      },
    ]
    

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

    evolve

    evolve<T, U>(rules: ((x: T) => U)[], list: T[]): U[]

    It takes object or array of functions as set of rules. These rules are applied to the iterable input to produce the result.

    All Typescript definitions
    evolve<T, U>(rules: ((x: T) => U)[], list: T[]): U[];
    evolve<T, U>(rules: ((x: T) => U)[]) : (list: T[]) => U[];
    evolve<E extends Evolver, V extends Evolvable<E>>(rules: E, obj: V): Evolve<V, E>;
    evolve<E extends Evolver>(rules: E): <V extends Evolvable<E>>(obj: V) => Evolve<V, E>;
    R.evolve source
    import { _isArray } from './_internals/_isArray'
    import { mapArray, mapObject } from './map'
    import { type } from './type'
    
    export function evolveArray(rules, list){
      return mapArray(
        (x, i) => {
          if (type(rules[ i ]) === 'Function'){
            return rules[ i ](x)
          }
    
          return x
        },
        list,
        true
      )
    }
    
    export function evolveObject(rules, iterable){
      return mapObject((x, prop) => {
        if (type(x) === 'Object'){
          const typeRule = type(rules[ prop ])
          if (typeRule === 'Function'){
            return rules[ prop ](x)
          }
          if (typeRule === 'Object'){
            return evolve(rules[ prop ], x)
          }
    
          return x
        }
        if (type(rules[ prop ]) === 'Function'){
          return rules[ prop ](x)
        }
    
        return x
      }, iterable)
    }
    
    export function evolve(rules, iterable){
      if (arguments.length === 1){
        return _iterable => evolve(rules, _iterable)
      }
      const rulesType = type(rules)
      const iterableType = type(iterable)
    
      if (iterableType !== rulesType){
        throw new Error('iterableType !== rulesType')
      }
    
      if (![ 'Object', 'Array' ].includes(rulesType)){
        throw new Error(`'iterable' and 'rules' are from wrong type ${ rulesType }`)
      }
    
      if (iterableType === 'Object'){
        return evolveObject(rules, iterable)
      }
    
      return evolveArray(rules, iterable)
    }
    Tests
    import { evolve as evolveRamda } from 'ramda'
    
    import { add } from '../rambda'
    import { compareCombinations, compareToRamda } from './_internals/testUtils'
    import { evolve } from './evolve'
    
    test('happy', () => {
      const rules = {
        foo    : add(1),
        nested : { bar : x => Object.keys(x).length },
      }
      const input = {
        a      : 1,
        foo    : 2,
        nested : { bar : { z : 3 } },
      }
      const result = evolve(rules, input)
      expect(result).toEqual({
        a      : 1,
        foo    : 3,
        nested : { bar : 1 },
      })
    })
    
    test('nested rule is wrong', () => {
      const rules = {
        foo    : add(1),
        nested : { bar : 10 },
      }
      const input = {
        a      : 1,
        foo    : 2,
        nested : { bar : { z : 3 } },
      }
      const result = evolve(rules)(input)
      expect(result).toEqual({
        a      : 1,
        foo    : 3,
        nested : { bar : { z : 3 } },
      })
    })
    
    test('is recursive', () => {
      const rules = {
        nested : {
          second : add(-1),
          third  : add(1),
        },
      }
      const object = {
        first  : 1,
        nested : {
          second : 2,
          third  : 3,
        },
      }
      const expected = {
        first  : 1,
        nested : {
          second : 1,
          third  : 4,
        },
      }
      const result = evolve(rules, object)
      expect(result).toEqual(expected)
    })
    
    test('ignores primitive values', () => {
      const rules = {
        n : 2,
        m : 'foo',
      }
      const object = {
        n : 0,
        m : 1,
      }
      const expected = {
        n : 0,
        m : 1,
      }
      const result = evolve(rules, object)
      expect(result).toEqual(expected)
    })
    
    test('with array', () => {
      const rules = [ add(1), add(-1) ]
      const list = [ 100, 1400 ]
      const expected = [ 101, 1399 ]
      const result = evolve(rules, list)
      expect(result).toEqual(expected)
    })
    
    const rulesObject = { a : add(1) }
    const rulesList = [ add(1) ]
    const possibleIterables = [ null, undefined, '', 42, [], [ 1 ], { a : 1 } ]
    const possibleRules = [ ...possibleIterables, rulesList, rulesObject ]
    
    describe('brute force', () => {
      compareCombinations({
        firstInput : possibleRules,
        callback   : errorsCounters => {
          expect(errorsCounters).toMatchInlineSnapshot(`
            Object {
              "ERRORS_MESSAGE_MISMATCH": 0,
              "ERRORS_TYPE_MISMATCH": 4,
              "RESULTS_MISMATCH": 0,
              "SHOULD_NOT_THROW": 51,
              "SHOULD_THROW": 0,
            }
          `)
        },
        secondInput : possibleIterables,
        fn          : evolve,
        fnRamda     : evolveRamda,
      })
    })
    Typescript test
    import {evolve, add} from 'rambda'
    
    describe('R.evolve', () => {
      it('happy', () => {
        const input = {
          foo: 2,
          nested: {
            a: 1,
            bar: 3,
          },
        }
        const rules = {
          foo: add(1),
          nested: {
            a: add(-1),
            bar: add(1),
          },
        }
        const result = evolve(rules, input)
        const curriedResult = evolve(rules)(input)
    
        result.nested.a // $ExpectType number
        curriedResult.nested.a // $ExpectType number
        result.nested.bar // $ExpectType number
        result.foo // $ExpectType number
      })
      it('with array', () => {
        const rules = [String, String]
        const input = [100, 1400]
        const result = evolve(rules, input)
        const curriedResult = evolve(rules)(input)
        result // $ExpectType string[]
        curriedResult // $ExpectType string[]
      })
    })

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

    F

    F(): boolean
    All Typescript definitions
    F(): boolean;
    R.F source
    export function F(){
      return false
    }

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

    filter

    filter<T>(predicate: Predicate<T>): (input: T[]) => T[]

    It filters list or object input using a predicate function.

    All Typescript definitions
    filter<T>(predicate: Predicate<T>): (input: T[]) => T[];
    filter<T>(predicate: Predicate<T>, input: T[]): T[];
    filter<T, U>(predicate: ObjectPredicate<T>): (x: Dictionary<T>) => Dictionary<T>;
    filter<T>(predicate: ObjectPredicate<T>, x: Dictionary<T>): Dictionary<T>;
    R.filter source
    import { _isArray } from './_internals/_isArray'
    
    export function filterObject(predicate, obj){
      const willReturn = {}
    
      for (const prop in obj){
        if (predicate(obj[ prop ], prop, obj)){
          willReturn[ prop ] = obj[ prop ]
        }
      }
    
      return willReturn
    }
    
    export function filterArray(
      predicate, list, indexed = false
    ){
      let index = 0
      const len = list.length
      const willReturn = []
    
      while (index < len){
        const predicateResult = indexed ?
          predicate(list[ index ], index) :
          predicate(list[ index ])
        if (predicateResult){
          willReturn.push(list[ index ])
        }
    
        index++
      }
    
      return willReturn
    }
    
    export function filter(predicate, iterable){
      if (arguments.length === 1){
        return _iterable => filter(predicate, _iterable)
      }
      if (!iterable) return []
      if (_isArray(iterable)) return filterArray(predicate, iterable)
    
      return filterObject(predicate, iterable)
    }
    Tests
    import Ramda from 'ramda'
    
    import { F } from './F'
    import { filter } from './filter'
    import { T } from './T'
    
    const sampleObject = {
      a : 1,
      b : 2,
      c : 3,
      d : 4,
    }
    
    test('happy', () => {
      const isEven = n => n % 2 === 0
    
      expect(filter(isEven, [ 1, 2, 3, 4 ])).toEqual([ 2, 4 ])
      expect(filter(isEven, {
        a : 1,
        b : 2,
        d : 3,
      })).toEqual({ b : 2 })
    })
    
    test('bad inputs difference between Ramda and Rambda', () => {
      expect(filter(T)(undefined)).toEqual([])
      expect(filter(F, null)).toEqual([])
      expect(() => Ramda.filter(T, null)).toThrowWithMessage(TypeError,
        'Cannot read property \'filter\' of null')
      expect(() => Ramda.filter(T, undefined)).toThrowWithMessage(TypeError,
        'Cannot read property \'filter\' of undefined')
    })
    
    test('predicate when input is object', () => {
      const obj = {
        a : 1,
        b : 2,
      }
      const predicate = (
        val, prop, inputObject
      ) => {
        expect(inputObject).toEqual(obj)
        expect(typeof prop).toEqual('string')
    
        return val < 2
      }
      expect(filter(predicate, obj)).toEqual({ a : 1 })
    })
    
    test('with object', () => {
      const isEven = n => n % 2 === 0
      const result = filter(isEven, sampleObject)
      const expectedResult = {
        b : 2,
        d : 4,
      }
    
      expect(result).toEqual(expectedResult)
    })
    Typescript test
    import {filter} from 'rambda'
    
    const list = [1, 2, 3]
    const obj = {a: 1, b: 2}
    
    describe('R.filter with array', () => {
      it('happy', () => {
        const result = filter<number>(x => {
          x // $ExpectType number
          return x > 1
        }, list)
        result // $ExpectType number[]
      })
      it('curried', () => {
        const result = filter<number>(x => {
          x // $ExpectType number
          return x > 1
        })(list)
        result // $ExpectType number[]
      })
    })
    
    describe('R.filter with objects', () => {
      it('happy', () => {
        const result = filter<number>((val, prop, origin) => {
          val // $ExpectType number
          prop // $ExpectType string
          origin // $ExpectType Dictionary<number>
    
          return val > 1
        }, obj)
        result // $ExpectType Dictionary<number>
      })
      it('curried version requires second dummy type', () => {
        const result = filter<number, any>((val, prop, origin) => {
          val // $ExpectType number
          prop // $ExpectType string
          origin // $ExpectType Dictionary<number>
    
          return val > 1
        })(obj)
        result // $ExpectType Dictionary<number>
      })
    })
    Lodash is fastest. Rambda is 6.7% slower and Ramda is 72.03% slower
    const R = require('../../dist/rambda.js')
    
    const arr = [ 1, 2, 3, 4 ]
    const fn = x => x > 2
    const filter = [
      {
        label : 'Rambda',
        fn    : () => {
          R.filter(fn, arr)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.filter(fn, arr)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.filter(arr, fn)
        },
      },
    ]
    

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

    find

    find<T>(predicate: (x: T) => boolean, list: T[]): T | undefined

    It returns the first element of list that satisfy the predicate.

    If there is no such element, it returns undefined.

    All Typescript definitions
    find<T>(predicate: (x: T) => boolean, list: T[]): T | undefined;
    find<T>(predicate: (x: T) => boolean): (list: T[]) => T | undefined;
    R.find source
    export function find(predicate, list){
      if (arguments.length === 1) return _list => find(predicate, _list)
    
      let index = 0
      const len = list.length
    
      while (index < len){
        const x = list[ index ]
        if (predicate(x)){
          return x
        }
    
        index++
      }
    }
    Tests
    import { find } from './find'
    import { propEq } from './propEq'
    
    const list = [ { a : 1 }, { a : 2 }, { a : 3 } ]
    
    test('happy', () => {
      const fn = propEq('a', 2)
      expect(find(fn, list)).toEqual({ a : 2 })
    })
    
    test('with curry', () => {
      const fn = propEq('a', 4)
      expect(find(fn)(list)).toBeUndefined()
    })
    
    test('with empty list', () => {
      expect(find(() => true, [])).toBeUndefined()
    })
    Typescript test
    import {find} from 'rambda'
    
    const list = [1, 2, 3]
    
    describe('R.find', () => {
      it('happy', () => {
        const predicate = (x: number) => x > 2
        const result = find(predicate, list)
        result // $ExpectType number | undefined
      })
      it('curried', () => {
        const predicate = (x: number) => x > 2
        const result = find(predicate)(list)
        result // $ExpectType number | undefined
      })
    })
    Rambda is fastest. Ramda is 85.14% slower and Lodash is 42.65% slower
    const R = require('../../dist/rambda.js')
    
    const fn = x => x > 2
    const list = [ 1, 2, 3, 4 ]
    
    const find = [
      {
        label : 'Rambda',
        fn    : () => {
          R.find(fn, list)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.find(fn, list)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.find(list, fn)
        },
      },
    ]
    

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

    findIndex

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

    It returns the index of the first element of list satisfying the predicate function.

    If there is no such element, then -1 is returned.

    All Typescript definitions
    findIndex<T>(predicate: (x: T) => boolean, list: T[]): number;
    findIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
    R.findIndex source
    export function findIndex(predicate, list){
      if (arguments.length === 1) return _list => findIndex(predicate, _list)
    
      const len = list.length
      let index = -1
    
      while (++index < len){
        if (predicate(list[ index ])){
          return index
        }
      }
    
      return -1
    }
    Tests
    import { findIndex } from './findIndex'
    import { propEq } from './propEq'
    
    const list = [ { a : 1 }, { a : 2 }, { a : 3 } ]
    
    test('happy', () => {
      expect(findIndex(propEq('a', 2), list)).toEqual(1)
    
      expect(findIndex(propEq('a', 1))(list)).toEqual(0)
    
      expect(findIndex(propEq('a', 4))(list)).toEqual(-1)
    })
    Typescript test
    import {findIndex} from 'rambda'
    
    const list = [1, 2, 3]
    
    describe('R.findIndex', () => {
      it('happy', () => {
        const predicate = (x: number) => x > 2
        const result = findIndex(predicate, list)
        result // $ExpectType number
      })
      it('curried', () => {
        const predicate = (x: number) => x > 2
        const result = findIndex(predicate)(list)
        result // $ExpectType number
      })
    })
    Rambda is fastest. Ramda is 86.48% slower and Lodash is 72.27% slower
    const R = require('../../dist/rambda.js')
    
    const fn = x => x > 2
    const list = [ 1, 2, 3, 4 ]
    
    const findIndex = [
      {
        label : 'Rambda',
        fn    : () => {
          R.findIndex(fn, list)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.findIndex(fn, list)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.findIndex(list, fn)
        },
      },
    ]
    

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

    findLast

    findLast<T>(fn: (x: T) => boolean, list: T[]): T | undefined

    It returns the last element of list satisfying the predicate function.

    If there is no such element, then undefined is returned.

    All Typescript definitions
    findLast<T>(fn: (x: T) => boolean, list: T[]): T | undefined;
    findLast<T>(fn: (x: T) => boolean): (list: T[]) => T | undefined;
    R.findLast source
    export function findLast(predicate, list){
      if (arguments.length === 1) return _list => findLast(predicate, _list)
    
      let index = list.length
    
      while (--index >= 0){
        if (predicate(list[ index ])){
          return list[ index ]
        }
      }
    
      return undefined
    }
    Tests
    import { findLast } from './findLast'
    
    test('happy', () => {
      const result = findLast(x => x > 1,
        [ 1, 1, 1, 2, 3, 4, 1 ])
      expect(result).toEqual(4)
    
      expect(findLast(x => x === 0, [ 0, 1, 1, 2, 3, 4, 1 ])).toEqual(0)
    })
    
    test('with curry', () => {
      expect(findLast(x => x > 1)([ 1, 1, 1, 2, 3, 4, 1 ])).toEqual(4)
    })
    
    const obj1 = { x : 100 }
    const obj2 = { x : 200 }
    const a = [ 11, 10, 9, 'cow', obj1, 8, 7, 100, 200, 300, obj2, 4, 3, 2, 1, 0 ]
    const even = function (x){
      return x % 2 === 0
    }
    const gt100 = function (x){
      return x > 100
    }
    const isStr = function (x){
      return typeof x === 'string'
    }
    const xGt100 = function (o){
      return o && o.x > 100
    }
    
    test('ramda 1', () => {
      expect(findLast(even, a)).toEqual(0)
      expect(findLast(gt100, a)).toEqual(300)
      expect(findLast(isStr, a)).toEqual('cow')
      expect(findLast(xGt100, a)).toEqual(obj2)
    })
    
    test('ramda 2', () => {
      expect(findLast(even, [ 'zing' ])).toEqual(undefined)
    })
    
    test('ramda 3', () => {
      expect(findLast(even, [ 2, 3, 5 ])).toEqual(2)
    })
    
    test('ramda 4', () => {
      expect(findLast(even, [])).toEqual(undefined)
    })
    Typescript test
    import {findLast} from 'rambda'
    
    const list = [1, 2, 3]
    
    describe('R.findLast', () => {
      it('happy', () => {
        const predicate = (x: number) => x > 2
        const result = findLast(predicate, list)
        result // $ExpectType number | undefined
      })
      it('curried', () => {
        const predicate = (x: number) => x > 2
        const result = findLast(predicate)(list)
        result // $ExpectType number | undefined
      })
    })

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

    findLastIndex

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

    It returns the index of the last element of list satisfying the predicate function.

    If there is no such element, then -1 is returned.

    All Typescript definitions
    findLastIndex<T>(predicate: (x: T) => boolean, list: T[]): number;
    findLastIndex<T>(predicate: (x: T) => boolean): (list: T[]) => number;
    R.findLastIndex source
    export function findLastIndex(fn, list){
      if (arguments.length === 1) return _list => findLastIndex(fn, _list)
    
      let index = list.length
    
      while (--index >= 0){
        if (fn(list[ index ])){
          return index
        }
      }
    
      return -1
    }
    Tests
    import { findLastIndex } from './findLastIndex'
    
    test('happy', () => {
      const result = findLastIndex(x => x > 1,
        [ 1, 1, 1, 2, 3, 4, 1 ])
    
      expect(result).toEqual(5)
    
      expect(findLastIndex(x => x === 0, [ 0, 1, 1, 2, 3, 4, 1 ])).toEqual(0)
    })
    
    test('with curry', () => {
      expect(findLastIndex(x => x > 1)([ 1, 1, 1, 2, 3, 4, 1 ])).toEqual(5)
    })
    
    const obj1 = { x : 100 }
    const obj2 = { x : 200 }
    const a = [ 11, 10, 9, 'cow', obj1, 8, 7, 100, 200, 300, obj2, 4, 3, 2, 1, 0 ]
    const even = function (x){
      return x % 2 === 0
    }
    const gt100 = function (x){
      return x > 100
    }
    const isStr = function (x){
      return typeof x === 'string'
    }
    const xGt100 = function (o){
      return o && o.x > 100
    }
    
    test('ramda 1', () => {
      expect(findLastIndex(even, a)).toEqual(15)
      expect(findLastIndex(gt100, a)).toEqual(9)
      expect(findLastIndex(isStr, a)).toEqual(3)
      expect(findLastIndex(xGt100, a)).toEqual(10)
    })
    
    test('ramda 2', () => {
      expect(findLastIndex(even, [ 'zing' ])).toEqual(-1)
    })
    
    test('ramda 3', () => {
      expect(findLastIndex(even, [ 2, 3, 5 ])).toEqual(0)
    })
    
    test('ramda 4', () => {
      expect(findLastIndex(even, [])).toEqual(-1)
    })
    Typescript test
    import {findLastIndex} from 'rambda'
    
    const list = [1, 2, 3]
    
    describe('R.findLastIndex', () => {
      it('happy', () => {
        const predicate = (x: number) => x > 2
        const result = findLastIndex(predicate, list)
        result // $ExpectType number
      })
      it('curried', () => {
        const predicate = (x: number) => x > 2
        const result = findLastIndex(predicate)(list)
        result // $ExpectType number
      })
    })

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

    flatten

    flatten<T>(list: any[]): T[]

    It deeply flattens an array.

    All Typescript definitions
    flatten<T>(list: any[]): T[];
    R.flatten source
    import { _isArray } from './_internals/_isArray'
    
    export function flatten(list, input){
      const willReturn = input === undefined ? [] : input
    
      for (let i = 0; i < list.length; i++){
        if (_isArray(list[ i ])){
          flatten(list[ i ], willReturn)
        } else {
          willReturn.push(list[ i ])
        }
      }
    
      return willReturn
    }
    Tests
    import { flatten } from './flatten'
    
    test('happy', () => {
      expect(flatten([ 1, 2, 3, [ [ [ [ [ 4 ] ] ] ] ] ])).toEqual([ 1, 2, 3, 4 ])
    
      expect(flatten([ 1, [ 2, [ [ 3 ] ] ], [ 4 ] ])).toEqual([ 1, 2, 3, 4 ])
    
      expect(flatten([ 1, [ 2, [ [ [ 3 ] ] ] ], [ 4 ] ])).toEqual([ 1, 2, 3, 4 ])
    
      expect(flatten([ 1, 2, [ 3, 4 ], 5, [ 6, [ 7, 8, [ 9, [ 10, 11 ], 12 ] ] ] ])).toEqual([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ])
    })
    
    test('readme example', () => {
      const result = flatten([ 1, 2, [ 3, 30, [ 300 ] ], [ 4 ] ])
      expect(result).toEqual([ 1, 2, 3, 30, 300, 4 ])
    })
    Typescript test
    import {flatten} from 'rambda'
    
    describe('flatten', () => {
      it('happy', () => {
        const result = flatten<number>([1, 2, [3, [4]]])
        result // $ExpectType number[]
      })
    })
    Rambda is fastest. Ramda is 95.26% slower and Lodash is 10.27% slower
    const R = require('../../dist/rambda.js')
    
    const list = [ 1, [ 2, [ 3, 4, 6 ] ] ]
    
    const flatten = [
      {
        label : 'Rambda',
        fn    : () => {
          R.flatten(list)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.flatten(list)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.flatten(list)
        },
      },
    ]
    

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

    flip

    It returns function which calls fn with exchanged first and second argument.

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

    forEach

    forEach<T>(fn: Iterator<T, void>, list: T[]): T[]

    It applies iterable function over all members of list and returns list.

    All Typescript definitions
    forEach<T>(fn: Iterator<T, void>, list: T[]): T[];
    forEach<T>(fn: Iterator<T, void>): (list: T[]) => T[];
    forEach<T>(fn: ObjectIterator<T, void>, list: Dictionary<T>): Dictionary<T>;
    forEach<T, U>(fn: ObjectIterator<T, void>): (list: Dictionary<T>) => Dictionary<T>;
    R.forEach source
    import { _isArray } from './_internals/_isArray'
    import { _keys } from './_internals/_keys'
    
    export function forEach(fn, list){
      if (arguments.length === 1) return _list => forEach(fn, _list)
    
      if (list === undefined){
        return
      }
    
      if (_isArray(list)){
        let index = 0
        const len = list.length
    
        while (index < len){
          fn(list[ index ])
          index++
        }
      } else {
        let index = 0
        const keys = _keys(list)
        const len = keys.length
    
        while (index < len){
          const key = keys[ index ]
          fn(
            list[ key ], key, list
          )
          index++
        }
      }
    
      return list
    }
    Tests
    import { forEach } from './forEach'
    import { type } from './type'
    
    test('happy', () => {
      const sideEffect = {}
      forEach(x => sideEffect[ `foo${ x }` ] = x + 10)([ 1, 2 ])
    
      expect(sideEffect).toEqual({
        foo1 : 11,
        foo2 : 12,
      })
    })
    
    test('iterate over object', () => {
      const obj = {
        a : 1,
        b : [ 1, 2 ],
        c : { d : 7 },
        f : 'foo',
      }
      const result = {}
      const returned = forEach((
        val, prop, inputObj
      ) => {
        expect(type(inputObj)).toBe('Object')
        result[ prop ] = `${ prop }-${ type(val) }`
      })(obj)
    
      const expected = {
        a : 'a-Number',
        b : 'b-Array',
        c : 'c-Object',
        f : 'f-String',
      }
    
      expect(result).toEqual(expected)
      expect(returned).toEqual(obj)
    })
    
    test('with empty list', () => {
      const list = []
      const result = forEach(x => x * x)(list)
    
      expect(result).toEqual(list)
    })
    
    test('with wrong input', () => {
      const list = undefined
      const result = forEach(x => x * x)(list)
    
      expect(result).toBeUndefined()
    })
    
    test('returns the input', () => {
      const list = [ 1, 2, 3 ]
      const result = forEach(x => x * x)(list)
    
      expect(result).toEqual(list)
    })
    Typescript test
    import {forEach} from 'rambda'
    
    const list = [1, 2, 3]
    const obj = {a: 1, b: 2}
    
    describe('R.forEach with arrays', () => {
      it('happy', () => {
        const result = forEach(a => {
          a // $ExpectType number
        }, list)
        result // $ExpectType number[]
      })
      it('curried require an explicit typing', () => {
        const result = forEach<number>(a => {
          a // $ExpectType number
        })(list)
        result // $ExpectType number[]
      })
    })
    
    describe('R.forEach with objects', () => {
      it('happy', () => {
        const result = forEach((a, b, c) => {
          a // $ExpectType number
          b // $ExpectType string
          c // $ExpectType Dictionary<number>
          return `${a}`
        }, obj)
        result // $ExpectType Dictionary<number>
      })
      it('curried require an input typing and a dummy third typing', () => {
        // Required in order all typings to work
        const result = forEach<number, any>((a, b, c) => {
          a // $ExpectType number
          b // $ExpectType string
          c // $ExpectType Dictionary<number>
        })(obj)
        result // $ExpectType Dictionary<number>
      })
      it('iterator without property', () => {
        const result = forEach(a => {
          a // $ExpectType number
        }, obj)
        result // $ExpectType Dictionary<number>
      })
    })

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

    fromPairs

    It transforms a listOfPairs to an object.

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

    groupBy

    It splits list according to a provided groupFn function and returns an object.

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

    groupWith

    groupWith<T>(compareFn: (x: T, y: T) => boolean): (input: T[]) => (T[])[]

    It returns separated version of list or string input, where separation is done with equality compareFn function.

    All Typescript definitions
    groupWith<T>(compareFn: (x: T, y: T) => boolean): (input: T[]) => (T[])[];
    groupWith<T>(compareFn: (x: T, y: T) => boolean, input: T[]): (T[])[];
    groupWith<T>(compareFn: (x: T, y: T) => boolean, input: string): string[];
    R.groupWith source
    import { _isArray } from './_internals/_isArray'
    
    export function groupWith(compareFn, list){
      if (!_isArray(list)) throw new TypeError('list.reduce is not a function')
    
      const clone = list.slice()
    
      if (list.length === 1) return [ clone ]
    
      const toReturn = []
      let holder = []
    
      clone.reduce((
        prev, current, i
      ) => {
        if (i === 0) return current
    
        const okCompare = compareFn(prev, current)
        const holderIsEmpty = holder.length === 0
        const lastCall = i === list.length - 1
    
        if (okCompare){
          if (holderIsEmpty) holder.push(prev)
          holder.push(current)
          if (lastCall) toReturn.push(holder)
    
          return current
        }
    
        if (holderIsEmpty){
          toReturn.push([ prev ])
          if (lastCall) toReturn.push([ current ])
    
          return current
        }
    
        toReturn.push(holder)
        if (lastCall) toReturn.push([ current ])
        holder = []
    
        return current
      }, undefined)
    
      return toReturn
    }
    Tests
    import { equals } from './equals'
    import { groupWith } from './groupWith'
    
    test('issue is fixed', () => {
      const result = groupWith(equals, [ 1, 2, 2, 3 ])
      const expected = [ [ 1 ], [ 2, 2 ], [ 3 ] ]
      expect(result).toEqual(expected)
    })
    
    test('long list', () => {
      const result = groupWith(equals, [
        0,
        1,
        1,
        2,
        3,
        5,
        8,
        13,
        21,
        21,
        21,
        1,
        2,
      ])
    
      const expected = [
        [ 0 ],
        [ 1, 1 ],
        [ 2 ],
        [ 3 ],
        [ 5 ],
        [ 8 ],
        [ 13 ],
        [ 21, 21, 21 ],
        [ 1 ],
        [ 2 ],
      ]
      expect(result).toEqual(expected)
    })
    
    test('readme example', () => {
      const list = [ 4, 3, 6, 2, 2, 1 ]
    
      const result = groupWith((a, b) => a - b === 1, list)
      const expected = [ [ 4, 3 ], [ 6 ], [ 2 ], [ 2, 1 ] ]
      expect(result).toEqual(expected)
    })
    
    test('throw with string as input', () => {
      expect(() => groupWith(equals, 'Mississippi')).toThrowWithMessage(TypeError,
        'list.reduce is not a function')
    })
    
    const isConsecutive = function (a, b){
      return a + 1 === b
    }
    
    test('fix coverage', () => {
      expect(groupWith(isConsecutive, [ 1, 2, 3, 0 ])).toEqual([ [ 1, 2, 3 ], [ 0 ] ])
    })
    
    test('from ramda 0', () => {
      expect(groupWith(equals, [])).toEqual([])
      expect(groupWith(isConsecutive, [])).toEqual([])
    })
    
    test('from ramda 1', () => {
      expect(groupWith(isConsecutive, [ 4, 3, 2, 1 ])).toEqual([
        [ 4 ],
        [ 3 ],
        [ 2 ],
        [ 1 ],
      ])
    })
    
    test('from ramda 2', () => {
      expect(groupWith(isConsecutive, [ 1, 2, 3, 4 ])).toEqual([ [ 1, 2, 3, 4 ] ])
    })
    
    test('from ramda 3', () => {
      expect(groupWith(isConsecutive, [ 1, 2, 2, 3 ])).toEqual([
        [ 1, 2 ],
        [ 2, 3 ],
      ])
      expect(groupWith(isConsecutive, [ 1, 2, 9, 3, 4 ])).toEqual([
        [ 1, 2 ],
        [ 9 ],
        [ 3, 4 ],
      ])
    })
    
    test('list with single item', () => {
      const result = groupWith(equals, [ 0 ])
    
      const expected = [ [ 0 ] ]
      expect(result).toEqual(expected)
    })
    Typescript test
    import {groupWith} from 'rambda'
    
    describe('R.groupWith', () => {
      it('happy', () => {
        const groupWithFn = (x: string, y: string) => x.length === y.length
        const list = ['foo', 'bar', 'bazzz']
    
        const result = groupWith(groupWithFn, list)
        const curriedResult = groupWith(groupWithFn)(list)
        result // $ExpectType string[][]
        curriedResult // $ExpectType string[][]
      })
    })

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

    has

    has<T>(prop: string, obj: T): boolean

    It returns true if obj has property prop.

    All Typescript definitions
    has<T>(prop: string, obj: T): boolean;
    has(prop: string): <T>(obj: T) => boolean;
    R.has source
    export function has(prop, obj) {
      if (arguments.length === 1) return _obj => has(prop, _obj)
    
      if (!obj) return false
    
      return obj.hasOwnProperty(prop)
    }
    Tests
    import { has } from './has'
    
    test('happy', () => {
      expect(has('a')({ a : 1 })).toBeTrue()
      expect(has('b', { a : 1 })).toBeFalse()
    })
    
    test('with non-object', () => {
      expect(has('a', undefined)).toEqual(false)
      expect(has('a', null)).toEqual(false)
      expect(has('a', true)).toEqual(false)
      expect(has('a', '')).toEqual(false)
      expect(has('a', /a/)).toEqual(false)
    })
    Typescript test
    import {has} from 'rambda'
    
    describe('R.has', () => {
      it('happy', () => {
        const result = has('foo', {a: 1})
        const curriedResult = has('bar')({a: 1})
        result // $ExpectType boolean
        curriedResult // $ExpectType boolean
      })
    })

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

    hasPath

    hasPath<T>(
      path: string | string[],
      input: object
    ): boolean

    It will return true, if input object has truthy path(calculated with R.path).

    All Typescript definitions
    hasPath<T>(
      path: string | string[],
      input: object
    ): boolean;
    hasPath<T>(
      path: string | string[]
    ): (input: object) => boolean;
    R.hasPath source
    import { path } from './path'
    
    export function hasPath(maybePath, obj){
      if (arguments.length === 1){
        return objHolder => hasPath(maybePath, objHolder)
      }
    
      return path(maybePath, obj) !== undefined
    }
    Tests
    import { hasPath } from './hasPath'
    
    test('when true', () => {
      const path = 'a.b'
      const obj = { a : { b : [] } }
    
      const result = hasPath(path)(obj)
      const expectedResult = true
    
      expect(result).toEqual(expectedResult)
    })
    
    test('when false', () => {
      const path = 'a.b'
      const obj = {}
    
      const result = hasPath(path, obj)
      const expectedResult = false
    
      expect(result).toEqual(expectedResult)
    })
    Typescript test
    import {hasPath} from 'rambda'
    
    describe('R.hasPath', () => {
      it('string path', () => {
        const obj = {a: {b: 1}}
        const result = hasPath('a.b', obj)
        const curriedResult = hasPath('a.c')(obj)
        result // $ExpectType boolean
        curriedResult // $ExpectType boolean
      })
      it('array path', () => {
        const obj = {a: {b: 1}}
        const result = hasPath(['a', 'b'], obj)
        const curriedResult = hasPath(['a', 'c'])(obj)
        result // $ExpectType boolean
        curriedResult // $ExpectType boolean
      })
    })

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

    head

    head<T>(input: T[]): T | undefined

    It returns the first element of list or string input.

    All Typescript definitions
    head<T>(input: T[]): T | undefined;
    head(input: string): string;
    R.head source
    export function head(listOrString){
      if (typeof listOrString === 'string') return listOrString[ 0 ] || ''
    
      return listOrString[ 0 ]
    }
    Tests
    import { head } from './head'
    
    test('head', () => {
      expect(head([ 'fi', 'fo', 'fum' ])).toEqual('fi')
      expect(head([])).toEqual(undefined)
      expect(head('foo')).toEqual('f')
      expect(head('')).toEqual('')
    })
    Typescript test
    import {head} from 'rambda'
    
    describe('R.head', () => {
      it('string', () => {
        const result = head('foo')
        result // $ExpectType string
      })
      it('array', () => {
        const result = head([1, 2, 3])
        result // $ExpectType number | undefined
      })
    })

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

    identical

    It returns true if its arguments a and b are identical.

    Otherwise, it returns false.

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

    identity

    identity<T>(input: T): T

    It just passes back the supplied input argument.

    All Typescript definitions
    identity<T>(input: T): T;
    R.identity source
    export function identity(input){
      return input
    }
    Tests
    import { identity } from './identity'
    
    test('happy', () => {
      expect(identity(7)).toEqual(7)
      expect(identity(true)).toEqual(true)
      expect(identity({ a : 1 })).toEqual({ a : 1 })
    })
    Typescript test
    import {identity} from 'rambda'
    
    describe('R.identity', () => {
      it('happy', () => {
        const result = identity(4)
        result // $ExpectType 4
      })
    })

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

    ifElse

    ifElse<T, U>(
      condition: (x: T) => boolean, 
      onTrue: (x: T) => U, 
      onFalse: (x: T) => U, 
    ): (x: T) => U

    It expects condition, onTrue and onFalse functions as inputs and it returns a new function with example name of fn.

    When fn`` is called with inputargument, it will return eitheronTrue(input)oronFalse(input)depending oncondition(input)` evaluation.

    All Typescript definitions
    ifElse<T, U>(
      condition: (x: T) => boolean, 
      onTrue: (x: T) => U, 
      onFalse: (x: T) => U, 
    ): (x: T) => U;
    ifElse<T, K, U>(
      condition: (x: T, y: K) => boolean, 
      onTrue: (x: T, y: K) => U, 
      onFalse: (x: T, y: K) => U, 
    ): (x: T, y: K) => U;
    R.ifElse source
    import { curry } from './curry'
    
    function ifElseFn(
      condition, onTrue, onFalse
    ){
      return (...input) => {
        const conditionResult =
          typeof condition === 'boolean' ? condition : condition(...input)
    
        if (conditionResult === true){
          return onTrue(...input)
        }
    
        return onFalse(...input)
      }
    }
    
    export const ifElse = curry(ifElseFn)
    Tests
    import { always } from './always'
    import { has } from './has'
    import { identity } from './identity'
    import { ifElse } from './ifElse'
    import { prop } from './prop'
    
    const condition = has('foo')
    const v = function (a){
      return typeof a === 'number'
    }
    const t = function (a){
      return a + 1
    }
    const ifFn = x => prop('foo', x).length
    const elseFn = () => false
    
    test('happy', () => {
      const fn = ifElse(condition, ifFn)(elseFn)
    
      expect(fn({ foo : 'bar' })).toEqual(3)
      expect(fn({ fo : 'bar' })).toEqual(false)
    })
    
    test('ramda spec', () => {
      const ifIsNumber = ifElse(v)
      expect(ifIsNumber(t, identity)(15)).toEqual(16)
      expect(ifIsNumber(t, identity)('hello')).toEqual('hello')
    })
    
    test('pass all arguments', () => {
      const identity = function (a){
        return a
      }
      const v = function (){
        return true
      }
      const onTrue = function (a, b){
        expect(a).toEqual(123)
        expect(b).toEqual('abc')
      }
      ifElse(
        v, onTrue, identity
      )(123, 'abc')
    })
    
    test('accept constant as condition', () => {
      const fn = ifElse(true)(always(true))(always(false))
    
      expect(fn()).toEqual(true)
    })
    
    test('accept constant as condition - case 2', () => {
      const fn = ifElse(
        false, always(true), always(false)
      )
    
      expect(fn()).toEqual(false)
    })
    
    test('curry 1', () => {
      const fn = ifElse(condition, ifFn)(elseFn)
    
      expect(fn({ foo : 'bar' })).toEqual(3)
      expect(fn({ fo : 'bar' })).toEqual(false)
    })
    
    test('curry 2', () => {
      const fn = ifElse(condition)(ifFn)(elseFn)
    
      expect(fn({ foo : 'bar' })).toEqual(3)
      expect(fn({ fo : 'bar' })).toEqual(false)
    })
    
    test('simple arity of 1', () => {
      const condition = x => x > 5
      const onTrue = x => x + 1
      const onFalse = x => x + 10
      const result = ifElse(
        condition, onTrue, onFalse
      )(1)
      expect(result).toBe(11)
    })
    
    test('simple arity of 2', () => {
      const condition = (x, y) => x + y > 5
      const onTrue = (x, y) => x + y + 1
      const onFalse = (x, y) => x + y + 10
      const result = ifElse(
        condition, onTrue, onFalse
      )(1, 10)
      expect(result).toBe(12)
    })
    Typescript test
    import {ifElse} from 'rambda'
    
    describe('R.ifElse', () => {
      it('happy', () => {
        const condition = (x: number) => x > 5
        const onTrue = (x: number) => `foo${x}`
        const onFalse = (x: number) => `bar${x}`
        const fn = ifElse(condition, onTrue, onFalse)
        fn // $ExpectType (x: number) => string
        const result = fn(3)
        result // $ExpectType string
      })
      it('arity of 2', () => {
        const condition = (x: number, y: string) => x + y.length > 5
        const onTrue = (x: number, y: string) => `foo${x}-${y}`
        const onFalse = (x: number, y: string) => `bar${x}-${y}`
        const fn = ifElse(condition, onTrue, onFalse)
        fn // $ExpectType (x: number, y: string) => string
        const result = fn(3, 'hello')
        result // $ExpectType string
      })
    })
    Rambda is faster than Ramda with 58.56%
    const R = require('../../dist/rambda.js')
    
    const condition = R.has('foo')
    const v = function (a){
      return typeof a === 'number'
    }
    const t = function (a){
      return a + 1
    }
    const ifFn = x => R.prop('foo', x).length
    const elseFn = () => false
    
    const ifElse = [
      {
        label : 'Rambda',
        fn    : () => {
          const fn = R.ifElse(condition, ifFn)(elseFn)
    
          fn({ foo : 'bar' })
          fn({ fo : 'bar' })
    
          const ifIsNumber = R.ifElse(v)
          ifIsNumber(t, R.identity)(15)
          ifIsNumber(t, R.identity)('hello')
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          const fn = Ramda.ifElse(condition, ifFn)(elseFn)
    
          fn({ foo : 'bar' })
          fn({ fo : 'bar' })
    
          const ifIsNumber = Ramda.ifElse(v)
          ifIsNumber(t, R.identity)(15)
          ifIsNumber(t, R.identity)('hello')
        },
      },
    ]
    

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

    inc

    It increments a number.

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

    includes

    includes(valueToFind: string, input: string[] | string): boolean

    If input is string, then this method work as native String.includes.

    If input is array, then R.equals is used to define if valueToFind belongs to the list.

    All Typescript definitions
    includes(valueToFind: string, input: string[] | string): boolean;
    includes(valueToFind: string): (input: string[] | string) => boolean;
    includes<T>(valueToFind: T, input: T[]): boolean;
    includes<T>(valueToFind: T): (input: T[]) => boolean;
    R.includes source
    import { _isArray } from './_internals/_isArray'
    import { _indexOf } from './indexOf'
    
    export function includes(valueToFind, input){
      if (arguments.length === 1) return _input => includes(valueToFind, _input)
      if (typeof input === 'string'){
        return input.includes(valueToFind)
      }
      if (!input){
        throw new TypeError(`Cannot read property \'indexOf\' of ${ input }`)
      }
      if (!_isArray(input)) return false
    
      return _indexOf(valueToFind, input) > -1
    }
    Tests
    import { includes } from './includes'
    import { includes as includesRamda } from 'ramda'
    
    test('with string as iterable', () => {
      const str = 'foo bar'
    
      expect(includes('bar')(str)).toBeTrue()
      expect(includesRamda('bar')(str)).toBeTrue()
      expect(includes('never', str)).toBeFalse()
      expect(includesRamda('never', str)).toBeFalse()
    })
    
    test('with array as iterable', () => {
      const arr = [ 1, 2, 3 ]
    
      expect(includes(2)(arr)).toBeTrue()
      expect(includesRamda(2)(arr)).toBeTrue()
    
      expect(includes(4, arr)).toBeFalse()
      expect(includesRamda(4, arr)).toBeFalse()
    })
    
    test('with list of objects as iterable', () => {
      const arr = [ {a:1}, {b:2}, {c:3} ]
    
      expect(includes({c:3}, arr)).toBeTrue()
      expect(includesRamda({c:3}, arr)).toBeTrue()
    })
    
    test('with NaN', () => {
      const result = includes(NaN, [NaN])
      const ramdaResult = includesRamda(NaN, [NaN])
      expect(result).toBeTrue()
      expect(ramdaResult).toBeTrue()
    })
    
    test('with wrong input that does not throw', () => {
      const result = includes(1, /foo/g)
      const ramdaResult = includesRamda(1, /foo/g)
      expect(result).toBeFalse()
      expect(ramdaResult).toBeFalse()
    })
    
    test('throws on wrong input - match ramda behaviour', () => {
      expect(() => includes(2, null)).toThrowWithMessage(TypeError,
        'Cannot read property \'indexOf\' of null')
      expect(() => includesRamda(2, null)).toThrowWithMessage(TypeError,
        'Cannot read property \'indexOf\' of null')
      expect(() => includes(2, undefined)).toThrowWithMessage(TypeError,
        'Cannot read property \'indexOf\' of undefined')
      expect(() => includesRamda(2, undefined)).toThrowWithMessage(TypeError,
        'Cannot read property \'indexOf\' of undefined')
    })
    Typescript test
    import {includes} from 'rambda'
    
    const list = [{a: {b: '1'}}, {a: {c: '2'}}, {a: {b: '3'}}]
    
    describe('R.includes', () => {
      it('happy', () => {
        const result = includes({a: {b: '1'}}, list)
        result // $ExpectType boolean
      })
      it('with string', () => {
        const result = includes('oo', 'foo')
        const curriedResult = includes('oo')('foo')
    
        result // $ExpectType boolean
        curriedResult // $ExpectType boolean
      })
    })
    Rambda is slower than Ramda with 6.14%
    const R = require('../../dist/rambda.js')
    // const R = require('rambdax')
    
    const mode = 0
    const limit = 10000
    
    const strings = Array(limit).fill(null).map(() => String(Math.floor(Math.random() * 1000)))
    
    const modes = [
      strings
    ]
    const activeMode = modes[mode]
    
    const includes = [
      {
        label : 'Rambda',
        fn    : () => {
          R.includes('0', activeMode)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.includes('0', activeMode)
        },
      },
    ]
    

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

    indexBy

    It generates object with properties provided by condition and values provided by list array.

    If condition is a function, then all list members are passed through it.

    If condition is a string, then all list members are passed through R.path(condition).

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

    indexOf

    It returns the index of the first element of list equals to valueToFind.

    If there is no such element, it returns -1.

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

    init

    init<T>(input: T[]): T[]

    It returns all but the last element of list or string input.

    All Typescript definitions
    init<T>(input: T[]): T[];
    init(input: string): string;
    R.init source
    import baseSlice from './_internals/baseSlice'
    
    export function init(listOrString){
      if (typeof listOrString === 'string') return listOrString.slice(0, -1)
    
      return listOrString.length ? baseSlice(
        listOrString, 0, -1
      ) : []
    }
    Tests
    import { init } from './init'
    
    test('with array', () => {
      expect(init([ 1, 2, 3 ])).toEqual([ 1, 2 ])
      expect(init([ 1, 2 ])).toEqual([ 1 ])
      expect(init([ 1 ])).toEqual([])
      expect(init([])).toEqual([])
      expect(init([])).toEqual([])
      expect(init([ 1 ])).toEqual([])
    })
    
    test('with string', () => {
      expect(init('foo')).toEqual('fo')
      expect(init('f')).toEqual('')
      expect(init('')).toEqual('')
    })
    Typescript test
    import {init} from 'rambda'
    
    describe('R.init', () => {
      it('with string', () => {
        const result = init('foo')
    
        result // $ExpectType string
      })
      it('with list', () => {
        const result = init([1, 2, 3])
    
        result // $ExpectType number[]
      })
    })
    Rambda is fastest. Ramda is 92.24% slower and Lodash is 13.3% slower
    const R = require('../../dist/rambda.js')
    
    const list = [ 1, 2, 3, 4 ]
    
    const init = [
      {
        label : 'Rambda',
        fn    : () => {
          R.init(list)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.init(list)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.initial(list)
        },
      },
    ]
    

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

    intersection

    It loops throw listA and listB and returns the intersection of the two according to R.equals.

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

    intersperse

    It adds a separator between members of list.

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

    is

    It returns true if x is instance of targetPrototype.

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

    isEmpty

    isEmpty<T>(x: T): boolean

    It returns true if x is empty.

    All Typescript definitions
    isEmpty<T>(x: T): boolean;
    R.isEmpty source
    import { type } from './type'
    
    export function isEmpty(input){
      const inputType = type(input)
      if ([ 'Undefined', 'NaN', 'Number', 'Null' ].includes(inputType))
        return false
      if (!input) return true
    
      if (inputType === 'Object'){
        return Object.keys(input).length === 0
      }
    
      if (inputType === 'Array'){
        return input.length === 0
      }
    
      return false
    }
    Tests
    import { isEmpty } from './isEmpty'
    
    test('happy', () => {
      expect(isEmpty(undefined)).toEqual(false)
      expect(isEmpty('')).toEqual(true)
      expect(isEmpty(null)).toEqual(false)
      expect(isEmpty(' ')).toEqual(false)
      expect(isEmpty(new RegExp(''))).toEqual(false)
      expect(isEmpty([])).toEqual(true)
      expect(isEmpty([ [] ])).toEqual(false)
      expect(isEmpty({})).toEqual(true)
      expect(isEmpty({ x : 0 })).toEqual(false)
      expect(isEmpty(0)).toEqual(false)
      expect(isEmpty(NaN)).toEqual(false)
      expect(isEmpty([ '' ])).toEqual(false)
    })
    Typescript test
    import {isEmpty} from 'rambda'
    
    describe('R.isEmpty', () => {
      it('happy', () => {
        const result = isEmpty('foo')
        result // $ExpectType boolean
      })
    })
    Rambda is fastest. Ramda is 97.14% slower and Lodash is 54.99% slower
    const R = require('../../dist/rambda.js')
    
    const isEmpty = [
      {
        label : 'Rambda',
        fn    : () => {
          R.isEmpty(undefined)
          R.isEmpty('')
          R.isEmpty(null)
          R.isEmpty(' ')
          R.isEmpty(new RegExp(''))
          R.isEmpty([])
          R.isEmpty([ [] ])
          R.isEmpty({})
          R.isEmpty({ x : 0 })
          R.isEmpty(0)
          R.isEmpty(NaN)
          R.isEmpty([ '' ])
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.isEmpty(undefined)
          Ramda.isEmpty('')
          Ramda.isEmpty(null)
          Ramda.isEmpty(' ')
          Ramda.isEmpty(new RegExp(''))
          Ramda.isEmpty([])
          Ramda.isEmpty([ [] ])
          Ramda.isEmpty({})
          Ramda.isEmpty({ x : 0 })
          Ramda.isEmpty(0)
          Ramda.isEmpty(NaN)
          Ramda.isEmpty([ '' ])
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.isEmpty(undefined)
          _.isEmpty('')
          _.isEmpty(null)
          _.isEmpty(' ')
          _.isEmpty(new RegExp(''))
          _.isEmpty([])
          _.isEmpty([ [] ])
          _.isEmpty({})
          _.isEmpty({ x : 0 })
          _.isEmpty(0)
          _.isEmpty(NaN)
          _.isEmpty([ '' ])
        },
      },
    ]
    

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

    isNil

    isNil(x: any): x is null | undefined

    It returns true if x is either null or undefined.

    All Typescript definitions
    isNil(x: any): x is null | undefined;
    R.isNil source
    export function isNil(x){
      return x === undefined || x === null
    }
    Tests
    import { isNil } from './isNil'
    
    test('happy', () => {
      expect(isNil(null)).toBeTrue()
    
      expect(isNil(undefined)).toBeTrue()
    
      expect(isNil([])).toBeFalse()
    })

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

    join

    join<T>(glue: string, list: T[]): string

    It returns a string of all list instances joined with a glue.

    All Typescript definitions
    join<T>(glue: string, list: T[]): string;
    join<T>(glue: string): (list: T[]) => string;
    R.join source
    export function join(glue, list){
      if (arguments.length === 1) return _list => join(glue, _list)
    
      return list.join(glue)
    }
    Tests
    import { join } from './join'
    
    test('curry', () => {
      expect(join('|')([ 'foo', 'bar', 'baz' ])).toEqual('foo|bar|baz')
    
      expect(join('|', [ 1, 2, 3 ])).toEqual('1|2|3')
    
      const spacer = join(' ')
    
      expect(spacer([ 'a', 2, 3.4 ])).toEqual('a 2 3.4')
    })
    Typescript test
    import {join} from 'rambda'
    
    describe('R.join', () => {
      it('happy', () => {
        const result = join('|', [1, 2, 3])
        result // $ExpectType string
      })
    })

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

    keys

    keys<T extends object>(x: T): (keyof T)[]

    It applies Object.keys over x and returns its keys.

    All Typescript definitions
    keys<T extends object>(x: T): (keyof T)[];
    keys<T>(x: T): string[];
    R.keys source
    export function keys(x){
      return Object.keys(x)
    }
    Tests
    import { keys } from './keys'
    
    test('happy', () => {
      expect(keys({ a : 1 })).toEqual([ 'a' ])
    })
    Typescript test
    import {keys} from 'rambda'
    
    const obj = {a: 1, b: 2}
    
    describe('R.keys', () => {
      it('happy', () => {
        const result = keys(obj)
        result // $ExpectType ("b" | "a")[]
      })
    })

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

    last

    last(str: string): string

    It returns the last element of input, as the input can be either a string or an array.

    All Typescript definitions
    last(str: string): string;
    last(emptyList: []): undefined;
    last<T extends any>(list: T[]): T;
    R.last source
    export function last(listOrString){
      if (typeof listOrString === 'string'){
        return listOrString[ listOrString.length - 1 ] || ''
      }
    
      return listOrString[ listOrString.length - 1 ]
    }
    Tests
    import { last } from './last'
    
    test('with list', () => {
      expect(last([ 1, 2, 3 ])).toBe(3)
      expect(last([])).toBeUndefined()
    })
    
    test('with string', () => {
      expect(last('abc')).toEqual('c')
      expect(last('')).toEqual('')
    })
    Typescript test
    import {last} from 'rambda'
    
    describe('R.last', () => {
      it('string', () => {
        const result = last('foo')
        result // $ExpectType string
      })
    
      it('array', () => {
        const result = last([1, 2, 3])
        result // $ExpectType number
      })
    
      it('empty array', () => {
        const result = last([])
        result // $ExpectType undefined
      })
    })
    Rambda is fastest. Ramda is 93.43% slower and Lodash is 5.28% slower
    const R = require('../../dist/rambda.js')
    
    const list = [ 1, 2, 3, 4 ]
    
    const last = [
      {
        label : 'Rambda',
        fn    : () => {
          R.last(list)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.last(list)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.last(list)
        },
      },
    ]
    

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

    lastIndexOf

    lastIndexOf<T>(target: T, list: T[]): number

    It returns the last index of target in list array.

    R.equals is used to determine equality between target and members of list.

    If there is no such index, then -1 is returned.

    All Typescript definitions
    lastIndexOf<T>(target: T, list: T[]): number;
    lastIndexOf<T>(target: T): (list: T[]) => number;
    R.lastIndexOf source
    import { equals } from './equals'
    
    export function lastIndexOf(target, list){
      if (arguments.length === 1) return _list => lastIndexOf(target, _list)
    
      let index = list.length
    
      while (--index > 0){
        if (equals(list[ index ], target)){
          return index
        }
      }
    
      return -1
    }
    Tests
    import { lastIndexOf } from './lastIndexOf'
    
    test('happy', () => {
      const a = lastIndexOf(1, [ 1, 2, 3, 1, 2 ])
      const b = lastIndexOf(1)([ 1, 2, 3, 1, 2 ])
    
      expect(a).toEqual(3)
      expect(b).toEqual(3)
    })
    
    test('false', () => {
      const a = lastIndexOf(10, [ 1, 2, 3, 1, 2 ])
    
      expect(a).toEqual(-1)
    })
    Typescript test
    import {lastIndexOf} from 'rambda'
    
    const list = [1, 2, 3]
    
    describe('R.lastIndexOf', () => {
      it('happy', () => {
        const result = lastIndexOf(2, list)
        result // $ExpectType number
      })
      it('curried', () => {
        const result = lastIndexOf(2)(list)
        result // $ExpectType number
      })
    })
    Rambda is faster than Ramda with 85.19%
    const R = require('../../dist/rambda.js')
    
    const isEven = n => n % 2 === 0
    const arr = [ 1, 3, 5, 7, 9, 11 ]
    
    const lastIndexOf = [
      {
        label : 'Rambda',
        fn    : () => {
          R.lastIndexOf(1, [ 1, 2, 3, 1, 2 ])
          R.lastIndexOf(1)([ 1, 2, 3, 1, 2 ])
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.lastIndexOf(1, [ 1, 2, 3, 1, 2 ])
          Ramda.lastIndexOf(1)([ 1, 2, 3, 1, 2 ])
        },
      },
    ]
    

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

    length

    length<T>(input: T[]): number

    It returns the length property of list or string input.

    All Typescript definitions
    length<T>(input: T[]): number;
    R.length source
    export function length(x){
      if (!x && x !== '' || x.length === undefined){
        return NaN
      }
    
      return x.length
    }
    Tests
    import { length } from './length'
    
    test('happy', () => {
      expect(length('foo')).toEqual(3)
      expect(length([ 1, 2, 3 ])).toEqual(3)
      expect(length([])).toEqual(0)
    })
    
    test('with empty string', () => {
      expect(length('')).toEqual(0)
    })
    
    test('with bad input returns NaN', () => {
      expect(length(0)).toBeNaN()
      expect(length({})).toBeNaN()
      expect(length(null)).toBeNaN()
      expect(length(undefined)).toBeNaN()
    })

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

    lens

    lens<T, U, V>(getter: (s: T) => U, setter: (a: U, s: T) => V): Lens

    It returns a lens for the given getter and setter functions.

    The getter gets the value of the focus; the setter sets the value of the focus.

    The setter should not mutate the data structure.

    All Typescript definitions
    lens<T, U, V>(getter: (s: T) => U, setter: (a: U, s: T) => V): Lens;
    R.lens source
    export function lens(getter, setter){
      return function (functor){
        return function (target){
          return functor(getter(target)).map(focus => setter(focus, target))
        }
      }
    }
    Typescript test
    import {lens, assoc} from 'rambda'
    
    interface Input {
      foo: string,
    }
    
    describe('R.lens', () => {
      it('happy', () => {
        const fn = lens<Input, string, string>((x: Input) => {
          x.foo // $ExpectType string
          return x.foo
        }, assoc('name'))
        fn // $ExpectType Lens
      })
    })

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

    lensIndex

    lensIndex(index: number): Lens

    It returns a lens that focuses on specified index.

    All Typescript definitions
    lensIndex(index: number): Lens;
    R.lensIndex source
    import { lens } from './lens'
    import { nth } from './nth'
    import { update } from './update'
    
    export function lensIndex(index){
      return lens(nth(index), update(index))
    }
    Tests
    import { compose } from './compose'
    import { keys } from './keys'
    import { lensIndex } from './lensIndex'
    import { over } from './over'
    import { set } from './set'
    import { view } from './view'
    
    const testList = [ { a : 1 }, { b : 2 }, { c : 3 } ]
    
    test('focuses list element at the specified index', () => {
      expect(view(lensIndex(0), testList)).toEqual({ a : 1 })
    })
    
    test('returns undefined if the specified index does not exist', () => {
      expect(view(lensIndex(10), testList)).toEqual(undefined)
    })
    
    test('sets the list value at the specified index', () => {
      expect(set(
        lensIndex(0), 0, testList
      )).toEqual([ 0, { b : 2 }, { c : 3 } ])
    })
    
    test('applies function to the value at the specified list index', () => {
      expect(over(
        lensIndex(2), keys, testList
      )).toEqual([ { a : 1 }, { b : 2 }, [ 'c' ] ])
    })
    
    test('can be composed', () => {
      const nestedList = [ 0, [ 10, 11, 12 ], 1, 2 ]
      const composedLens = compose(lensIndex(1), lensIndex(0))
    
      expect(view(composedLens, nestedList)).toEqual(10)
    })
    
    test('set s (get s) === s', () => {
      expect(set(
        lensIndex(0), view(lensIndex(0), testList), testList
      )).toEqual(testList)
    })
    
    test('get (set s v) === v', () => {
      expect(view(lensIndex(0), set(
        lensIndex(0), 0, testList
      ))).toEqual(0)
    })
    
    test('get (set(set s v1) v2) === v2', () => {
      expect(view(lensIndex(0),
        set(
          lensIndex(0), 11, set(
            lensIndex(0), 10, testList
          )
        ))).toEqual(11)
    })
    Typescript test
    import {view, lensIndex} from 'rambda'
    
    interface Input {
      a: number,
    }
    const testList: Input[] = [{a: 1}, {a: 2}, {a: 3}]
    
    describe('R.lensIndex', () => {
      it('happy', () => {
        const result = view<Input[], Input>(lensIndex(0), testList)
        result // $ExpectType Input
        result.a // $ExpectType number
      })
    })

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

    lensPath

    lensPath(path: RamdaPath): Lens

    It returns a lens that focuses on specified path.

    All Typescript definitions
    lensPath(path: RamdaPath): Lens;
    lensPath(path: string): Lens;
    R.lensPath source
    import {assocPath} from './assocPath'
    import {lens} from './lens'
    import {path} from './path'
    
    export function lensPath(key) {
      return lens(path(key), assocPath(key))
    }
    Tests
    import { compose } from './compose'
    import { identity } from './identity'
    import { inc } from './inc'
    import { lensPath } from './lensPath'
    import { lensProp } from './lensProp'
    import { over } from './over'
    import { set } from './set'
    import { view } from './view'
    
    const testObj = {
      a : [ { b : 1 }, { b : 2 } ],
      d : 3,
    }
    
    test('view', () => {
      expect(view(lensPath('d'), testObj)).toEqual(3)
      expect(view(lensPath('a.0.b'), testObj)).toEqual(1)
      // this is different to ramda, as ramda will return a clone of the input object
      expect(view(lensPath(''), testObj)).toEqual(undefined)
    })
    
    test('set', () => {
      expect(set(
        lensProp('d'), 0, testObj
      )).toEqual({
        a : [ { b : 1 }, { b : 2 } ],
        d : 0,
      })
      expect(set(
        lensPath('a.0.b'), 0, testObj
      )).toEqual({
        a : [ { b : 0 }, { b : 2 } ],
        d : 3,
      })
      expect(set(
        lensPath('a.0.X'), 0, testObj
      )).toEqual({
        a : [
          {
            b : 1,
            X : 0,
          },
          { b : 2 },
        ],
        d : 3,
      })
      expect(set(
        lensPath([]), 0, testObj
      )).toEqual(0)
    })
    
    test('over', () => {
      expect(over(
        lensPath('d'), inc, testObj
      )).toEqual({
        a : [ { b : 1 }, { b : 2 } ],
        d : 4,
      })
      expect(over(
        lensPath('a.1.b'), inc, testObj
      )).toEqual({
        a : [ { b : 1 }, { b : 3 } ],
        d : 3,
      })
      expect(over(
        lensProp('X'), identity, testObj
      )).toEqual({
        a : [ { b : 1 }, { b : 2 } ],
        d : 3,
        X : undefined,
      })
      expect(over(
        lensPath('a.0.X'), identity, testObj
      )).toEqual({
        a : [
          {
            b : 1,
            X : undefined,
          },
          { b : 2 },
        ],
        d : 3,
      })
    })
    
    test('compose', () => {
      const composedLens = compose(lensPath('a'), lensPath('1.b'))
      expect(view(composedLens, testObj)).toEqual(2)
    })
    
    test('set s (get s) === s', () => {
      expect(set(
        lensPath([ 'd' ]), view(lensPath([ 'd' ]), testObj), testObj
      )).toEqual(testObj)
      expect(set(
        lensPath([ 'a', 0, 'b' ]),
        view(lensPath([ 'a', 0, 'b' ]), testObj),
        testObj
      )).toEqual(testObj)
    })
    
    test('get (set s v) === v', () => {
      expect(view(lensPath([ 'd' ]), set(
        lensPath([ 'd' ]), 0, testObj
      ))).toEqual(0)
      expect(view(lensPath([ 'a', 0, 'b' ]), set(
        lensPath([ 'a', 0, 'b' ]), 0, testObj
      ))).toEqual(0)
    })
    
    test('get (set(set s v1) v2) === v2', () => {
      const p = [ 'd' ]
      const q = [ 'a', 0, 'b' ]
      expect(view(lensPath(p), set(
        lensPath(p), 11, set(
          lensPath(p), 10, testObj
        )
      ))).toEqual(11)
      expect(view(lensPath(q), set(
        lensPath(q), 11, set(
          lensPath(q), 10, testObj
        )
      ))).toEqual(11)
    })
    Typescript test
    import {lensPath, view} from 'rambda'
    
    interface Input {
      foo: number[],
      bar: {
        a: string,
        b: string,
      },
    }
    
    const testObject: Input = {
      foo: [1, 2],
      bar: {
        a: 'x',
        b: 'y',
      },
    }
    
    const path = lensPath(['bar', 'a'])
    const pathAsString = lensPath('bar.a')
    
    describe('R.lensPath', () => {
      it('happy', () => {
        const result = view<Input, string>(path, testObject)
        result // $ExpectType string
      })
      it('using string as path input', () => {
        const result = view<Input, string>(pathAsString, testObject)
        result // $ExpectType string
      })
    })

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

    lensProp

    lensProp(prop: string): {
      <T, U>(obj: T): U

    It returns a lens that focuses on specified property prop.

    All Typescript definitions
    lensProp(prop: string): {
      <T, U>(obj: T): U;
      set<T, U, V>(val: T, obj: U): V;
    };
    R.lensProp source
    import { assoc } from './assoc'
    import { lens } from './lens'
    import { prop } from './prop'
    
    export function lensProp(key){
      return lens(prop(key), assoc(key))
    }
    Tests
    import { compose } from './compose'
    import { identity } from './identity'
    import { inc } from './inc'
    import { lensProp } from './lensProp'
    import { over } from './over'
    import { set } from './set'
    import { view } from './view'
    
    const testObj = {
      a : 1,
      b : 2,
      c : 3,
    }
    
    test('focuses object the specified object property', () => {
      expect(view(lensProp('a'), testObj)).toEqual(1)
    })
    
    test('returns undefined if the specified property does not exist', () => {
      expect(view(lensProp('X'), testObj)).toEqual(undefined)
    })
    
    test('sets the value of the object property specified', () => {
      expect(set(
        lensProp('a'), 0, testObj
      )).toEqual({
        a : 0,
        b : 2,
        c : 3,
      })
    })
    
    test('adds the property to the object if it doesn\'t exist', () => {
      expect(set(
        lensProp('d'), 4, testObj
      )).toEqual({
        a : 1,
        b : 2,
        c : 3,
        d : 4,
      })
    })
    
    test('applies function to the value of the specified object property', () => {
      expect(over(
        lensProp('a'), inc, testObj
      )).toEqual({
        a : 2,
        b : 2,
        c : 3,
      })
    })
    
    test('applies function to undefined and adds the property if it doesn\'t exist', () => {
      expect(over(
        lensProp('X'), identity, testObj
      )).toEqual({
        a : 1,
        b : 2,
        c : 3,
        X : undefined,
      })
    })
    
    test('can be composed', () => {
      const nestedObj = {
        a : { b : 1 },
        c : 2,
      }
      const composedLens = compose(lensProp('a'), lensProp('b'))
    
      expect(view(composedLens, nestedObj)).toEqual(1)
    })
    
    test('set s (get s) === s', () => {
      expect(set(
        lensProp('a'), view(lensProp('a'), testObj), testObj
      )).toEqual(testObj)
    })
    
    test('get (set s v) === v', () => {
      expect(view(lensProp('a'), set(
        lensProp('a'), 0, testObj
      ))).toEqual(0)
    })
    
    test('get (set(set s v1) v2) === v2', () => {
      expect(view(lensProp('a'),
        set(
          lensProp('a'), 11, set(
            lensProp('a'), 10, testObj
          )
        ))).toEqual(11)
    })
    Typescript test
    import {lensProp, view} from 'rambda'
    
    interface Input {
      foo: string,
    }
    
    const testObject: Input = {
      foo: 'Led Zeppelin',
    }
    
    const lens = lensProp('foo')
    
    describe('R.lensProp', () => {
      it('happy', () => {
        const result = view<Input, string>(lens, testObject)
        result // $ExpectType string
      })
    })

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

    map

    map<T, U>(fn: ObjectIterator<T, U>, iterable: Dictionary<T>): Dictionary<U>

    It returns the result of looping through iterable with fn.

    It works with both array and object.

    All Typescript definitions
    map<T, U>(fn: ObjectIterator<T, U>, iterable: Dictionary<T>): Dictionary<U>;
    map<T, U>(fn: Iterator<T, U>, iterable: T[]): U[];
    map<T, U>(fn: Iterator<T, U>): (iterable: T[]) => U[];
    map<T, U, S>(fn: ObjectIterator<T, U>): (iterable: Dictionary<T>) => Dictionary<U>;
    map<T>(fn: Iterator<T, T>): (iterable: T[]) => T[];
    map<T>(fn: Iterator<T, T>, iterable: T[]): T[];
    R.map source
    import { _isArray } from './_internals/_isArray'
    import { _keys } from './_internals/_keys'
    
    export function mapArray(
      fn, list, isIndexed = false
    ){
      let index = 0
      const willReturn = Array(list.length)
    
      while (index < list.length){
        willReturn[ index ] = isIndexed ? fn(list[ index ], index) : fn(list[ index ])
    
        index++
      }
    
      return willReturn
    }
    
    export function mapObject(fn, obj){
      let index = 0
      const keys = _keys(obj)
      const len = keys.length
      const willReturn = {}
    
      while (index < len){
        const key = keys[ index ]
        willReturn[ key ] = fn(
          obj[ key ], key, obj
        )
        index++
      }
    
      return willReturn
    }
    
    export const mapObjIndexed = mapObject
    
    export function map(fn, list){
      if (arguments.length === 1) return _list => map(fn, _list)
      if (list === undefined) return []
      if (_isArray(list)) return mapArray(fn, list)
    
      return mapObject(fn, list)
    }
    Tests
    import { map } from './map'
    
    const double = x => x * 2
    
    describe(`with array`, () => {
      test('happy', () => {
        expect(map(double, [ 1, 2, 3 ])).toEqual([ 2, 4, 6 ])
      })
    
      test('when undefined instead of array', () => {
        /**
         * https://github.com/selfrefactor/rambda/issues/77
         */
        expect(map(double)(undefined)).toEqual([])
      })
    })
    
    describe(`with object`, () => {
      const obj = {
        a : 1,
        b : 2,
      }
    
      test('happy', () => {
        expect(map(double, obj)).toEqual({
          a : 2,
          b : 4,
        })
      })
      test('property as second and input object as third argument', () => {
        const obj = {
          a : 1,
          b : 2,
        }
        const iterator = (
          val, prop, inputObject
        ) => {
          expect(prop).toBeString()
          expect(inputObject).toEqual(obj)
      
          return val * 2
        }
      
        expect(map(iterator)(obj)).toEqual({
          a : 2,
          b : 4,
        })
      })
    })
    Typescript test
    import {map} from 'rambda'
    
    describe('R.map with arrays', () => {
      it('iterable returns the same type as the input', () => {
        const result = map<number>(
          (x: number) => {
            x // $ExpectType number
            return x + 2
          },
          [1, 2, 3]
        )
        result // $ExpectType number[]
      })
      it('iterable returns the same type as the input - curried', () => {
        const result = map<number>((x: number) => {
          x // $ExpectType number
          return x + 2
        })([1, 2, 3])
        result // $ExpectType number[]
      })
      it('iterable returns different type as the input', () => {
        const result = map<number, string>(
          (x: number) => {
            x // $ExpectType number
            return String(x)
          },
          [1, 2, 3]
        )
        result // $ExpectType string[]
      })
    })
    
    describe('R.map with objects', () => {
      it('iterable with all three arguments - curried', () => {
        // It requires dummy third typing argument
        // in order to identify compared to curry typings for arrays
        // ============================================
        const result = map<number, string, any>((a, b, c) => {
          a // $ExpectType number
          b // $ExpectType string
          c // $ExpectType Dictionary<number>
          return `${a}`
        })({a: 1, b: 2})
        result // $ExpectType Dictionary<string>
      })
      it('iterable with all three arguments', () => {
        const result = map<number, string>(
          (a, b, c) => {
            a // $ExpectType number
            b // $ExpectType string
            c // $ExpectType Dictionary<number>
            return `${a}`
          },
          {a: 1, b: 2}
        )
        result // $ExpectType Dictionary<string>
      })
      it('iterable with property argument', () => {
        const result = map<number, string>(
          (a, b) => {
            a // $ExpectType number
            b // $ExpectType string
            return `${a}`
          },
          {a: 1, b: 2}
        )
        result // $ExpectType Dictionary<string>
      })
      it('iterable with no property argument', () => {
        const result = map<number, string>(
          a => {
            a // $ExpectType number
            return `${a}`
          },
          {a: 1, b: 2}
        )
        result // $ExpectType Dictionary<string>
      })
    })
    Rambda is fastest. Ramda is 86.6% slower and Lodash is 11.73% slower
    const R = require('../../dist/rambda.js')
    
    const arr = [ 1, 2, 3, 4 ]
    const fn = x => x * 2
    const map = [
      {
        label : 'Rambda',
        fn    : () => {
          R.map(fn, arr)
        },
      },
      {
        label : 'Ramda',
        fn    : () => {
          Ramda.map(fn, arr)
        },
      },
      {
        label : 'Lodash',
        fn    : () => {
          _.map(arr, fn)
        },
      },
    ]
    

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

    mapObjIndexed

    It works the same way as R.map does for objects. It is added as Ramda also has this method.

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

    match

    match(regExpression: RegExp, str: string): string[]

    Curried version of String.prototype.match which returns empty array, when there is no match.

    All Typescript definitions
    match(regExpression: RegExp, str: string): string[];
    match(regExpression: RegExp): (str: string) => string[];
    R.match source
    export function match(pattern, input){
      if (arguments.length === 1) return _input => match(pattern, _input)
    
      const willReturn = input.match(pattern)
    
      return willReturn === null ? [] : willReturn
    }
    Tests
    import { equals } from './equals'
    import { match } from './match'
    
    test('happy', () => {
      expect(match(/a./g)('foo bar baz')).toEqual([ 'ar', 'az' ])
    })
    
    test('fallback', () => {
      expect(match(/a./g)('foo')).toEqual([])
    })
    
    test('with string', () => {
      expect(match('a', 'foo')).toEqual([])
      expect(equals(match('o', 'foo'), [ 'o' ])).toBeTrue()
    })
    
    test('throwing', () => {
      expect(() => {
        match(/a./g, null)
      })</