iiris

A functional JavaScript utility library

Usage no npm install needed!

<script type="module">
  import iiris from 'https://cdn.skypack.dev/iiris';
</script>

README

Iiris 👁️

CI MIT License NPM

Iiris is an experimental utility library, designed to make it easier to manipulate built-in JavaScript data types like arrays, objects and strings in a functional manner. It is heavily inspired by projects like Ramda and Lodash.

Features & Goals

  • No mutation of input data.
  • Automatically curried, data-last API.
  • Performance on par with native JavaScript methods.
  • Good out-of-the-box TypeScript typings.
  • Small footprint (4 kB gzipped) and excellent tree-shaking support.
  • Support only native JavaScript data types.
  • Target reasonably current JavaScript environments (Node 10+)

Iiris is still alpha-quality software, so bugs and changes to the API should be expected.

If you've tried Iiris and something doesn't seem to be working as expected, let me know!

Table of Contents

Installation

Run either

$ npm install iiris

or

$ yarn add iiris

depending on your favourite package manager.

Getting started

import * as I from 'iiris'

By convention, Iiris is imported to a single-letter variable I. Here's a small showcase of its main features:

// Problem: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9.
// The sum of these multiples is 23.
//
// Find the sum of all the multiples of 3 or 5 below 1000.
//
// See https://projecteuler.net/problem=1 for more information
const sumOfMultiples = I.pipe(
  I.range(1, 1000),
  I.filter((n) => n % 3 === 0 || n % 5 === 0),
  I.sum
) // => 233168

Why Iiris?

Iiris is heavily inspired by libraries like Ramda and Lodash. However, there are a few things that make it different:

Compared to lodash:

  • Each function is automatically curried and input data is always the last argument.
  • Input data is never mutated.
  • Chaining is achieved with function composition instead of special constructs like _.chain.
  • Iiris doesn't support any kind of iteratee shorthands.

Compared to Ramda:

  • Much better TypeScript support. Typically, you don't have to add any extra type annotations when using Iiris, even when writing code in point-free style.
  • Iiris functions are less polymorphic. For example, I.map operates only on arrays, while R.map supports arrays, objects and arbitrary fantasy-land functors. TypeScript doesn't have native support for higher-kinded types (although some people have tried to work around that), so I made an intentional decision to limit the polymorphism of Iiris functions. This makes code less general but dramatically improves the TypeScript experience and makes tree-shaking more effective.
  • No support for placeholders. Placeholders add some overhead to each curried function call and make writing TypeScript typings much harder.
  • A bigger focus on performance.

Compared to both:

  • Iiris requires a fairly modern JavaScript engine (Node 10+) to run.

API Reference

Note that we display only the fully curried type signature for all curried functions. Unless otherwise specified, each function is curried and you may pass the desired number of arguments, depending on the context.

For example, I.add has an arity of 2, so the following are equivalent.

I.add(x, y) === I.add(x)(y)

I.equalsBy has an arity of 3, so all the following are equivalent.

I.equalsBy(fn, a, b)
I.equalsBy(fn, a)(b)
I.equalsBy(fn)(a, b)
I.equalsBy(fn)(a)(b)

Many of the type signatures are also simplified. As an example, we don't show the readonly modifier for each array argument.

Basic array operations

append

<T>(value: T) => (array: T[]) => T[]

Append a new element to the end of an array.

Example:

I.append(4, [1, 2, 3])
// => [1, 2, 3, 4]

See also: prepend, concat


concat

<T>(array: T[]) => (other: T[]) => T[]

Concatenate two arrays together.

Example:

I.concat([1, 2, 3], [4, 5, 6])
// => [1, 2, 3, 4, 5, 6]

See also: append, prepend


forEach

<T>(fn: (value: T) => void) => (array: T[]) => T[]

Apply fn to each element of the array and return the array.

Example:

I.forEach(console.log, ['h', 'i', '!'])
h
i
!
// => ['h', 'i', '!']

See also: forEachWithIndex


forEachWithIndex

<T>(fn: (index: number, value: T) => void) => (array: T[]) => T[]

Like forEach, but fn also receives the element index as the first argument.

Example:

I.forEachWithIndex(console.log, ['h', 'i', '!'])
0 h
1 i
2 !
// => ['h', 'i', '!']

See also: forEach


head

<T>(array: T[]) => T | undefined

Return the first element of the array or undefined.

Example:

I.head([1, 2, 3])
// => 1

I.head([])
// => undefined

See also: tail, init, last


init

<T>(array: T[]) => T[]

Return all elements of the array except the last.

Example:

I.init([1, 2, 3])
// => [1, 2]

I.init([])
// => []

See also: last, head, tail


isEmpty

<T>(array: T[]) => boolean

Check if array is empty.

Example:

I.isEmpty([1, 2, 3])
// => false

I.isEmpty([])
// => true

See also: length


last

<T>(array: T[]) => T | undefined

Return the last element of the array or undefined.

Example:

I.last([1, 2, 3])
// => 3

I.last([])
// => undefined

See also: init, head, tail


length

<T>(array: T[]) => number

Return the length of an array.

Example:

I.length([1, 2, 3])
// => 3

I.length([])
// => 0

See also: isEmpty


modifyNth

(index: number) => <T>(fn: (value: T) => T) => (array: T[]) => T[]

Returns a copy of array where the nth element has been replaced by applying fn to its current value.

  • If index is not within array bounds, the array is returned unchanged.
  • Removes the element if fn returns undefined.

Example:

I.modifyNth(0, I.inc, [1, 2, 3])
// => [2, 2, 3]

I.modifyNth(-1, I.inc, [1, 2, 3])
// => [1, 2, 4]

I.modifyNth(0, I.noop, [1, 2, 3])
// => [2, 3]

I.modifyNth(999, I.inc, [1, 2, 3])
// => [1, 2, 3]

See also: setNth, removeNth


nth

(index: number) => <T>(array: T[]) => T | undefined

Return the nth element from array or undefined.

Example:

I.nth(0, [1, 2, 3])
// => 1

I.nth(0, [])
// => undefined

See also: nthOr, prop


nthEquals

(index: number) => <T>(value: T) => (array: T[]) => boolean

Check if the nth element of array equals value, using equals for determining equality.

Example:

I.nthEquals(0, 'a', ['a', 'b', 'c'])
// => true

See also: nthSatisfies


nthOr

<T>(defaultValue: T) => (index: number) => (array: T[]) => T

Like nth, but if the resolved value is undefined, defaultValue is returned instead.

Example:

I.nthOr(999, 0, [1, 2, 3])
// => 1

I.nthOr(999, 0, [])
// => 999

I.nthOr(999, 0, [undefined])
// => 999

See also: nth, propOr


nthSatisfies

(index: number) => <T>(predicate: (value: T) => boolean) => (array: T[]) => boolean

Check if the nth element of array satisfies the predicate.

Example:

I.nthSatisfies(0, I.gt(0), [1, 2, 3])
// => true

See also: nthSatisfies


prepend

<T>(value: T) => (array: T[]) => T[]

Prepend a new element to the beginning of an array.

Example:

I.prepend(0, [1, 2, 3])
// => [0, 1, 2, 3]

See also: append, concat


removeNth

(index: number) => <T>(array: T[]) => T[]

Returns a copy of array without the nth element.

  • If index is not within the array bounds, the array is returned unchanged.

Example:

I.removeNth(0, [1, 2, 3])
// => [2, 3]

I.removeNth(-1, [1, 2, 3])
// => [1, 2]

I.removeNth(999, [1, 2, 3])
// => [1, 2, 3]

See also: modifyNth, setNth


setNth

(index: number) => <T>(value: undefined | T) => (array: T[]) => T[]

Returns a copy of array where nth element has been replaced with value.

  • If index is not within the array bounds, the array is returned unchanged.
  • Removes the element if value is undefined.

Example:

I.setNth(0, 999, [1, 2, 3])
// => [999, 2, 3]

I.setNth(-1, 999, [1, 2, 3])
// => [1, 2, 999]

I.setNth(999, 999, [1, 2, 3])
// => [1, 2, 3]

I.setNth(0, undefined, [1, 2, 3])
// => [2, 3]

See also: modifyNth, removeNth


tail

<T>(array: T[]) => T[]

Return all elements of the array except the first.

Example:

I.tail([1, 2, 3])
// => [2, 3]

I.tail([])
// => []

See also: head, init, last


Transforming arrays

flatMap

<T, U>(fn: (value: T) => U[]) => (array: T[]) => U[]

Return an array containing the results of applying fn to each element in the original array and then flattening the result by one level.

Example:

I.flatMap((n) => [n, n], [1, 2, 3])
// => [1, 1, 2, 2, 3, 3]

See also: map, flatten


flatten

<D extends number>(depth: D) => <T extends unknown[]>(array: T) => Array<FlatArray<T, D>>

Flatten a nested array by n levels.

Example:

I.flatten(1, [1, [2, [3]]])
// => [1, 2, [3]]

I.flatten(2, [1, [2, [3]]])
// => [1, 2, 3]

See also: flatMap


intersperse

<T>(separator: T) => (array: T[]) => T[]

Return a copy of array with separator inserted between each element.

Example:

I.intersperse(',', ['a', 'b', 'c'])
// => ['a', ',', 'b', ',', 'c']

I.intersperse(',', [])
// => []

See also: join


join

(separator: string) => <T>(array: T[]) => string

Convert the array to a string, inserting the separator between each element.

Example:

I.join(', ', [1, 2, 3])
// => '1, 2, 3'

See also: split, intersperse


map

<T, U>(fn: (value: T) => U) => (array: T[]) => U[]

Return an array containing the results of applying fn to each element in the original array.

Example:

I.map(I.inc, [1, 2, 3])
// => [2, 3, 4]

See also: mapWithIndex, mapMaybe, flatMap


mapMaybe

<T, U>(fn: (value: T) => U | undefined) => (array: T[]) => U[]

Return an array containing the results of applying fn to each element in the original array, discarding any undefined values.

Example:

const users = [
  { name: 'Alice', age: 10 },
  { name: 'Bob', age: undefined },
  { name: 'Carol', age: 20 }
]

I.mapMaybe(I.prop('age'), users)
// => [10, 20]

See also: map


mapWithIndex

<T, U>(fn: (index: number, value: T) => U) => (array: T[]) => U[]

Like map, but fn also receives the element index as the first argument.

Example:

I.mapWithIndex((i, c) => `${i}-${c}`, ['a', 'b', 'c'])
// => ['0-a', '1-b', '2-c']

See also: map


reverse

<T>(array: T[]) => T[]

Reverse an array.

Example:

I.reverse([1, 2, 3])
// => [3, 2, 1]

Reducing arrays

maximum

<T extends Ordered>(array: T[]) => T | undefined

Return the largest element of an array or undefined.

Example:

I.maximum([1, 2, 3])
// => 3

I.maximum([])
// => undefined

See also: minimum, maximumBy


maximumBy

<T, U extends Ordered>(fn: (value: T) => U) => (array: T[]) => T | undefined

Like maximum, but apply fn to each value before determining their ordering.

Example:

const users = [
  { name: 'Alice', age: 10 },
  { name: 'Bob', age: 20 },
  { name: 'Carol', age: 30 },
]

I.maximumBy((u) => u.age, users)
// => { name: 'Carol', age: 30 }

See also: maximum, minimumBy


minimum

<T extends Ordered>(array: T[]) => T | undefined

Return the smallest element of array or undefined.

Example:

I.minimum([1, 2, 3])
// => 1

I.minimum([])
// => undefined

See also: maximum, minimumBy


minimumBy

<T, U extends Ordered>(fn: (value: T) => U) => (array: T[]) => T | undefined

Like minimum, but fn is applied to each value before determining their ordering.

Example:

const users = [
  { name: 'Alice', age: 10 },
  { name: 'Bob', age: 20 },
  { name: 'Carol', age: 30 },
]

I.minimumBy((u) => u.age, users)
// => { name: 'Alice', age: 10 }

See also: minimum, maximumBy


reduce

<T, R>(reducer: (accumulator: R, value: T) => R) => (initial: R) => (array: T[]) => R

Left-associative fold.

Combine the elements of an array in to a single value by calling reducer with the accumulated value so far and the current element. The first call to reducer receives initial as the accumulator.

If the array is empty, initial is returned.

Example:

I.reduce((sum, n) => sum + n, 1, [2, 3, 4]) // equal to ((1 + 2) + 3) + 4
// => 10

See also: reduceRight


reduceRight

<T, R>(reducer: (value: T, accumulator: R) => R) => (initial: R) => (array: T[]) => R

Right-associative fold.

Combine the elements of an array in to a single value by calling reducer with the current element and the accumulated value so far. The first call to reducer receives initial as the accumulator.

If the array is empty, initial is returned.

Example:

I.reduceRight((n, sum) => n + sum, 4, [1, 2, 3]) // equal to 1 + (2 + (3 + 4))
// => 10

See also: reduce


sum

(numbers: number[]) => number

Sum an array of numbers together. Returns 0 if the array is empty.

Uses the Kahan summation algorithm for minimizing numerical error.

Example:

const numbers = I.repeat(0.1, 10)
// => [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]

I.sum(numbers)
// => 1

numbers.reduce((sum, n) => sum + n, 0)
// => 0.9999999999999999

See also: sumBy


sumBy

<T>(fn: (value: T) => number) => (array: T[]) => number

Like sum, but each element of the array is converted to a number by applying fn.

Example:

I.sumBy(I.prop('age'), [{ name: 'Alice', age: 10 }, { name: 'Bob', age: 20 }])
// => 30

See also: sum


Searching arrays with a predicate

count

<T>(predicate: (value: T) => boolean) => (array: T[]) => number

Count the number of elements in the array the satisfy the predicate.

Example:

I.count((n) => n > 1, [1, 2, 3])
// => 2

See also: filter


every

<T>(predicate: (value: T) => boolean) => (array: T[]) => boolean

Check if every element in the array satisfies the predicate.

Example:

I.every((n) => n < 10, [1, 2, 3])
// => true

I.every((n) => n < 3, [1, 2, 3])
// => false

See also: none, some


filter

<T>(predicate: (value: T) => boolean) => (array: T[]) => T[]
<T, U>(guard: (value: T) => value is U) => (array: T[]) => U[]

Return the elements of the array that satisfy the predicate.

Example:

I.filter((n) => n > 1, [1, 2, 3])
// => [2, 3]

See also: filterWithIndex, count, partition


filterWithIndex

<T>(predicate: (index: number, value: T) => boolean) => (array: T[]) => T[]

Like filter, but predicate also receives the element index as the first argument.

Example:

I.filterWithIndex((i, n) => i + n === 3, [1, 2, 3])
// => [2]

See also: filter


find

<T>(predicate: (value: T) => boolean) => (array: T[]) => T | undefined
<T, U>(guard: (value: T) => value is U) => (array: T[]) => U | undefined

Find the first element in the array that satisfies the predicate.

Returns undefined if none of the elements match.

Example:

I.find((c) => c !== 'a', ['a', 'b', 'c'])
// => 'b'

I.find((c) => c === 'x', ['a', 'b', 'c'])
// => undefined

See also: findLast, findIndex


findIndex

<T>(predicate: (value: T) => boolean) => (array: T[]) => number

Find the index of the first element in the array that satisfies the predicate.

Returns -1 if none of the elements satisfy the predicate.

Example:

I.findIndex((c) => c !== 'a', ['a', 'b', 'c'])
// => 1

I.findIndex((c) => c === 'x', ['a', 'b', 'c'])
// => -1

See also: findLastIndex, find


findLast

<T>(predicate: (value: T) => boolean) => (array: T[]) => T | undefined
<T, U>(guard: (value: T) => value is U) => (array: T[]) => U | undefined

Find the last element in the array that satisfies the predicate.

Returns undefined if none of the elements match.

Example:

I.findLast((c) => c !== 'a', ['a', 'b', 'c'])
// => 'c'

I.findLast((c) => c === 'x', ['a', 'b', 'c'])
// => undefined

See also: find, findLastIndex


findLastIndex

<T>(predicate: (value: T) => boolean) => (array: T[]) => number

Find the index of the last element in the array that satisfies the predicate.

Returns -1 if none of the elements match.

Example:

I.findLastIndex((c) => c !== 'a', ['a', 'b', 'c'])
// => 2

I.findLastIndex((c) => c === 'x', ['a', 'b', 'c'])
// => -1

See also: findIndex, findLast


none

<T>(predicate: (value: T) => boolean) => (array: T[]) => boolean

Check if none of the elements in the array satisfy the predicate.

Example:

I.none((n) => n > 5, [1, 2, 3])
// => true

I.none((n) => n > 5, [1, 2, 3])
// => false

See also: every, some


partition

<T>(predicate: (value: T) => boolean) => (array: T[]) => [T[], T[]]
<T, U>(guard: (value: T) => value is U) => (array: T[]) => [U[], Array<Exclude<T, U>>]

Partition the array into two arrays, the first containing the elements that satisfy the predicate and the second containing the elements that do not.

Example:

const [evens, odds] = I.partition((n) => n % 2 === 0, [1, 2, 3])
// => [[2], [1, 3]]

See also: filter


some

<T>(predicate: (value: T) => boolean) => (array: T[]) => boolean

Check if some elements in the array satisfies the predicate.

Example:

I.some((n) => n > 2, [1, 2, 3])
// true

I.some((n) => n > 5, [1, 2, 3])
// false

See also: every, none


Searching arrays by value

includes

<T>(value: T) => (array: T[]) => boolean

Check if the array includes the specified value, using equals for determining equality.

Example:

I.includes(1, [1, 2, 3])
// => true

I.includes(0, [1, 2, 3])
// => false

indexOf

<T>(value: T) => (array: T[]) => number

Return the index of the first element equaling value, using equals for determining equality. Returns -1 if no match can be found.

Example:

I.indexOf('b', ['a', 'b', 'c', 'a', 'b', 'c'])
// => 1

I.indexOf('x', ['a', 'b', 'c', 'a', 'b', 'c'])
// => -1

See also: lastIndexOf, includes


lastIndexOf

<T>(value: T) => (array: T[]) => number

Return the index of the last element equaling value, using equals for determining equality. Returns -1 if no match can be found.

Example:

I.lastIndexOf('b', ['a', 'b', 'c', 'a', 'b', 'c'])
// => 4

I.lastIndexOf('x', ['a', 'b', 'c', 'a', 'b', 'c'])
// => -1

See also: indexOf, includes


Grouping arrays by key

countBy

<T, K extends string>(keyFn: (value: T) => K) => (array: T[]) => Record<K, number>

Apply keyFn to each element in the array and return an object of counts by key.

Example:

const users = [
  { name: 'Alice' },
  { name: 'Bob' },
  { name: 'Alice' }
]

I.countBy(I.prop('name'), users)
// => { Alice: 2, Bob: 1 }

See also: groupBy


groupBy

<T, K extends string>(keyFn: (value: T) => K) => (array: T[]) => Record<K, T[]>

Partition the array into an object of arrays according to keyFn.

Example:

const users = [
  { name: 'Alice' },
  { name: 'Bob' },
  { name: 'Alice' },
]

I.groupBy(I.prop('name'), users)
// => { Alice: [{ name: 'Alice' }, { name: 'Alice' }], Bob: [{ name: 'Bob' }] }

See also: indexBy, countBy, groupMap, groupMapReduce


groupMap

<T, U>(mapFn: (value: T) => U) => <K extends string>(keyFn: (value: T) => K) => (array: T[]) => Record<K, U[]>

Like groupBy, but also apply mapFn to each element before adding it to the corresponding array.

Example:

const users = [
  { name: 'Alice', age: 10 },
  { name: 'Bob', age: 20 },
  { name: 'Alice', age: 30 }
]
const agesByName = I.groupMap(I.prop('age'), I.prop('name'), users)
// => { Alice: [10, 30], Bob: [20] }

See also: groupBy, groupMapReduce


groupMapReduce

<U>(reducer: (accumulator: U, value: U) => U) => <T>(mapFn: (value: T) => U) => <K extends string>(keyFn: (value: T) => K) => (array: T[]) => Record<K, U>

Like groupMap, but instead of returning an object of arrays, combine elements mapping to the same key with reducer.

Example:

const users = [
  { name: 'Alice', age: 10 },
  { name: 'Bob', age: 20 },
  { name: 'Alice', age: 30 }
]
const sumOfAgesByName = I.groupMapReduce(I.add, I.prop('age'), I.prop('name'), users)
// => { Alice: 40, Bob: 20 }

See also: groupBy, groupMap


indexBy

<T, K extends string>(keyFn: (value: T) => K) => (array: T[]) => Record<K, T>

Apply keyFn to each element in the array and return an object of elements indexed by each key.

If multiple elements map to the same key, the last one is selected.

Example:

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Carol' }
]
I.indexBy(I.prop('id'), users)
// => { '1': { id: 1, name: 'Carol' }, '2': { id: 2, name: 'Bob' } }

See also: groupBy


Building arrays

of

<T>(value: T) => [T]

Create a singleton array containing value

Example:

I.of(1)
// => [1]

See also: pair


pair

<T>(first: T) => <U>(second: U) => [T, U]

Create two element array containing first and second.

Example:

I.pair(1, 2)
// => [1, 2]

See also: of


range

(start: number) => (end: number) => number[]

Create an array of numbers between start (inclusive) and end (exclusive).

Example:

I.range(0, 10)
// => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// I.range(0, 0)
// => []

See also: times, repeat


repeat

<T>(value: T) => (n: number) => T[]

Repeat the given value n times.

Example:

I.repeat('a', 5)
// => ['a', 'a', 'a', 'a', 'a']

See also: range, times


times

<T>(fn: (index: number) => T) => (n: number) => T[]

Create an array of length n by applying fn to the index of each element.

Example:

I.times((n) => n * 10, 3)
// => [0, 10, 20]

See also: range, repeat


Slicing arrays

drop

(n: number) => <T>(array: T[]) => T[]

Drop the first n elements of an array.

Example:

I.drop(1, [1, 2, 3])
// => [2, 3]

I.drop(2, [1, 2, 3])
// => [3]

See also: dropLast, take


dropLast

(n: number) => <T>(array: T[]) => T[]

Drop the last n elements of an array.

Example:

I.dropLast(1, [1, 2, 3])
// => [1, 2]

I.dropLast(2, [1, 2, 3])
// => [1]

See also: drop, takeLast


dropLastWhile

<T>(predicate: (value: T) => boolean) => (array: T[]) => T[]

Drop elements from the end of an array while predicate is satisfied.

Example:

I.dropLastWhile((n) => n > 1, [1, 2, 3])
// => [1]

See also: dropWhile, takeLastWhile


dropWhile

<T>(predicate: (value: T) => boolean) => (array: T[]) => T[]

Drop elements from the beginning of an array while predicate is satisfied.

Example:

I.dropWhile((n) => n === 1, [1, 2, 3])
// => [2, 3]

See also: dropLastWhile, takeWhile


slice

(start: number) => (end: number) => <T>(array: T[]) => T[]

Create a copy of array containing the elements from start (inclusive) to end (exclusive).

Example:

I.slice(0, 2, [1, 2, 3])
// => [1, 2]

I.slice(1, 2, [1, 2, 3])
// => [2]

take

(n: number) => <T>(array: T[]) => T[]

Take the first n elements of an array.

Example:

I.take(2, [1, 2, 3])
// => [1, 2]

See also: drop, takeLast


takeLast

<T>(n: number) => (array: T[]) => T[]

Take the last n elements of an array.

Example:

I.takeLast(2, [1, 2, 3])
// => [2, 3]

See also: dropLast, take


takeLastWhile

<T>(predicate: (value: T) => boolean) => (array: T[]) => T[]

Take elements from the end of an array while predicate is satisfied.

Example:

I.takeLastWhile((n) => n >= 2, [1, 2, 3])
// => [2, 3]

See also: dropLastWhile, takeWhile


takeWhile

<T>(predicate: (value: T) => boolean) => (array: T[]) => T[]

Take elements from the beginning of an array while predicate is satisfied.

Example:

I.takeWhile((n) => n <= 2, [1, 2, 3])
// => [1, 2]

See also: dropWhile, takeLastWhile


Sorting arrays

ascend

<T, U extends Ordered>(fn: (value: T) => U) => (first: T, second: T) => number

Given a fn that maps a value to an Ordered value, create an ascending comparator function.

Note: The returned function is not curried.

Example:

I.sort(I.ascend(I.prop('age')), [{ name: 'Bob' }, { name: 'Alice' }])
// => [{ name: 'Alice' }, { name: 'Bob' }]

See also: descend, sort, sortWith


descend

<T, U extends Ordered>(fn: (value: T) => U) => (first: T, second: T) => number

Given a fn that maps a value to an Ordered value, create a descending comparator function.

Note: The returned function is not curried.

Example:

I.sort(I.descend(I.prop('name')), [{ name: 'Alice' }, { name: 'Bob' }])
// => [{ name: 'Bob' }, { name: 'Alice' }]

See also: ascend, sort, sortWith


sort

<T>(comparator: (first: T, second: T) => number) => (array: T[]) => T[]

Sort an array according to the comparator function.

Example:

I.sort((a, b) => a - b, [3, 2, 1])
// => [1, 2, 3]

See also: sortBy, sortWith, ascend, descend


sortBy

<T, U extends Ordered>(fn: (value: T) => U) => (array: T[]) => T[]

Sort an array into ascending order by mapping each element of the array with fn.

Example:

const users = [
  { name: 'Bob', age: 10 },
  { name: 'Alice', age: 20 }
]

I.sortBy(I.prop('name'), users)
// => [{ name: 'Alice', age: 20 }, { name: 'Bob', age: 10 }]

I.sortBy(I.prop('age'), users)
// => [{ name: 'Bob', age: 10 }, { name: 'Alice', age: 20 }]

See also: sort, sortWith


sortWith

<T>(comparators: Array<(first: T, second: T) => number>) => (array: T[]) => T[]

Sort an array according to an array of comparator functions.

The comparators are tried in order until an ordering has been found.

Example:

const users = [
  { name: 'Alice', age: 10 },
  { name: 'Bob', age: 20 },
  { name: 'Alice', age: 20 },
]

I.sortWith([I.descend(I.prop('age')), I.ascend(I.prop('name'))], users)
// => [{ name: 'Alice', age: 20 }, { name: 'Bob', age: 20 }, { name: 'Alice', age: 10 }]

See also: sort, sortBy, ascend, descend


Zipping arrays

zip

<T>(first: T[]) => <U>(second: U[]) => Array<[T, U]>

Combine the corresponding elements of two arrays into an array of pairs.

If one of the arrays is longer than the other, the extra elements are ignored.

Example:

I.zip(['a', 'b', 'c'], [1, 2, 3])
// => [['a', 1], ['b', 2], ['c', 3]]

See also: zipWith, zipObject


zipObject

<K extends string>(keys: K[]) => <T>(values: T[]) => Record<K, T>

Combine an array of keys and values into an object.

If one of the arrays is longer than the other, its extra elements are ignored.

Example:

I.zipObject(['a', 'b', 'c'], [1, 2, 3])
// => { a: 1, b: 2, c: 3 }

See also: zip, fromEntries


zipWith

<T, U, R>(fn: (value: T, other: U) => R) => (first: T[]) => (second: U[]) => R[]

Like zip, but the elements are combined with fn instead of constructing a pair.

Example:

I.zipWith(I.add, [1, 2, 3], [4, 5, 6])
// => [5, 7, 9]

See also: zip


Set operations

difference

<T>(first: T[], second: T[]) => T[]

Calculate the set difference between the first array and the second array, using equals for determining equality.

Will not remove duplicates from the first array.

Example:

I.difference([1, 2, 3], [2, 3, 4])
// => [1]

See also: differenceWith, union, intersection


differenceWith

<T>(equals: (value: T, other: T) => boolean) => (array: T[]) => (other: T[]) => T[]

Like difference, but using a custom equality function.

Example:

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Carol' },
]
const otherUsers = [
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Carol' },
  { id: 4, name: 'Dan' }
]

I.differenceWith((a, b) => a.id === b.id, users, otherUsers)
// => [ { id: 1, name: 'Alice' } ]

See also: difference, unionWith, intersectionWith


intersection

<T>(first: T[]) => (second: T[]) => T[]

Calculate the set intersection between the first array and the second array, using equals for determining equality.

Will not remove duplicates from the first array.

Example:

I.intersection([1, 2, 3], [2, 3, 4])
// => [2, 3]

See also: intersectionWith, union, difference


intersectionWith

<T>(equals: (value: T, other: T) => boolean) => (array: T[]) => (other: T[]) => T[]

Like intersection, but using a custom equality function.

Example:

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Carol' },
]
const otherUsers = [
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Carol' },
  { id: 4, name: 'Dan' }
]

I.intersectionWith((a, b) => a.id === b.id, users, otherUsers)
// => [ { id: 2, name: 'Bob' }, { id: 3, name: 'Carol' } ]

See also: intersection, unionWith, differenceWith


union

<T>(first: T[]) => (second: T[]) => T[]

Calculate the set union between the first array and the second array, using equals for determining equality.

Will not remove duplicates from the first array.

Example:

I.union([1, 2, 3], [2, 3, 4])
// => [1, 2, 3, 4]

See also: unionWith, intersection, difference


unionWith

<T>(equals: (value: T, other: T) => boolean) => (array: T[]) => (other: T[]) => T[]

Like union, but using a custom equality function.

Example:

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Carol' },
]
const otherUsers = [
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Carol' },
  { id: 4, name: 'Dan' }
]

I.unionWith((a, b) => a.id === b.id, users, otherUsers)
// => [ { id: 1, name: 'Alice' },  { id: 2, name: 'Bob' }, { id: 3, name: 'Carol' }, { id: 4, name: 'Dan' } ]

See also: union, intersectionWith, differenceWith


uniq

<T>(array: T[]) => T[]

Remove duplicate values from array, using equals for determining equality.

Example:

I.uniq([1, 2, 3, 1, 2, 3])
// => [1, 2, 3]

See also: uniqWith


uniqWith

<T>(equals: (value: T, other: T) => boolean) => (array: T[]) => T[]

Like uniq, but using a custom equality function.

Example:

const users = [
  { id: 1, name: 'Alice' },
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
]
I.uniqWith((a, b) => a.id === b.id, users)
// => [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

See also: uniq


Object

entries

<T extends object, K extends string>(object: T) => Array<[K, T[K]]>

Return an array of the own enumerable property key-value pairs of object

Example:

I.entries({ a: 1, b: 2, c: 3 })
// => [['a', 1], ['b', 2], ['c', 3]]

See also: fromEntries, keys, values


fromEntries

<K extends string, T>(entries: Array<[K, T]>) => Record<K, T>

Create an object from an array of [key, value] pairs.

Example:

I.fromEntries([['a', 1], ['b', 2], ['c', 3]])
// => { a: 1, b: 2, c: 3 }

See also: entries


has

<K extends string>(key: K) => (object: unknown) => object is Record<K, unknown>

Check if key is an own property of object.

Example:

I.has('a', { a: 1 })
// => true

I.has('toString', { a: 1 })
// => false

keys

<T extends object>(object: T) => Array<keyof T & string>

Return an array of the own enumerable property keys of object.

Example:

I.keys({ a: 1, b: 2, c: 3 })
// => ['a', 'b', 'c']

See also: entries, values


mapKeys

<K1 extends string, K2 extends string>(fn: (value: K1) => K2) => <V>(object: Record<K1, V>) => Record<K2, V>

Return an object containing the results of applying fn to each key of the original object.

If multiple keys map to the same new key, the latest value is selected.

Example:

I.mapKeys((k) => k.toUpperCase(), { a: 1, b: 2, c: 3 })
// => { A: 1, B: 2, C: 3 }

mapValues

<V1, V2>(fn: (value: V1) => V2) => <K extends string>(object: Record<K, V1>) => Record<K, V2>

Return an object containing the results of applying fn to each value of the original object.

Example:

I.mapValues(I.inc, { a: 1, b: 2, c: 3 })
// => { a: 2, b: 3, c: 4 }

merge

<T extends object>(first: T) => <U extends object>(second: U) => T & U

Copy the own enumerable properties of two objects, preferring the values from second in case of duplicate keys.

Example:

I.merge({ a: 1, b: 1 }, { b: 2, c: 2 })
// => { a: 1, b: 2, c: 2 }

modifyProp

<K extends string>(key: K) => <V>(fn: (value: V) => V) => <T extends HasKey<K, V>>(object: T) => T

Return a copy of object where the property key has replaced by applying fn to its current value.

  • If key is not an own property of object, the object is returned unchanged.
  • If fn returns undefined, the property is removed.

Example:

I.modifyProp('a', (n) => n + 1, { a: 1, b: 2, c: 3 })
// => { a: 2, b: 2, c: 3 }

I.modifyProp('a', () => undefined, { a: 1, b: 2, c: 3 })
// => { b: 2, c: 3 }

I.modifyProp('d', () => 4, { a: 1, b: 2, c: 3 })
// => { a: 1, b: 2, c: 3, d: 4 }

See also: setProp, removeProp


omit

<K extends string>(keys: K[]) => <T extends HasKey<K>>(object: T) => Omit<T, Extract<keyof T, K>>

Return a copy of object without the specified keys.

Example:

I.omit(['a', 'b'], { a: 1, b: 2, c: 3 })
// => { c: 3 }

See also: pick


pick

<K extends string>(keys: K[]) => <T extends HasKey<K>>(object: T) => Pick<T, Extract<keyof T, K>>

Return a copy of object with only the specified keys.

Example:

I.pick(['a', 'b'], { a: 1, b: 2, c: 3 })
// => { a: 1, b: 2 }

See also: omit


prop

<K extends string>(key: K) => <T extends HasKey<K>>(object: T) => T[K]

Retrieves the property key from object or undefined.

Example:

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

I.prop('a', {})
// => undefined

See also: propOr, at


propEquals

<K extends string>(key: K) => <V>(value: V) => <T extends HasKey<K, V>>(object: T) => boolean

Check if property key of object equals value, using equals for determining equality.

Example:

const users = [
  { name: 'Alice' },
  { name: 'Bob' },
]

I.some(I.propEquals('name', 'Alice'), users)
// => true

See also: propEquals


propOr

<V>(defaultValue: V) => <K extends string>(key: K) => <T extends HasKey<K, V>>(object: T) => V | Defined<T[K]>

Like prop, but if the resolved value is undefined, defaultValue is returned instead.

Example:

I.propOr(999, 'a', { a: 1, b: 2, c: 3 })
// => 1

I.propOr(999, 'a', {})
// => 999

I.propOr(999, 'a', { a: undefined })
// => 999

See also: prop, nthOr


propSatisfies

<K extends string>(key: K) => <V>(predicate: (value: V) => boolean) => <T extends HasKey<K, V>>(object: T) => boolean

Check if property key of object satisfies the predicate.

Example:

const users = [
  { name: 'Alice' },
  { name: 'Bob' },
]

I.some(I.propSatisfies('name', I.test(/^A/)), users)
// => true

See also: propEquals


removeProp

<K extends string>(key: K) => <T extends HasKey<K>>(object: T) => Omit<T, K>

Return a copy of object without the property key.

  • If key is not an own property of object, the object is returned unchanged.

Example:

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

setProp

<K extends string>(key: K) => <V>(value: V) => <T extends HasKey<K, V>>(object: T) => T

Return a copy of object with property key set to value.

  • If value is undefined, the property is removed.

Example:

I.setProp('a', 999, { a: 1, b: 2, c: 3 })
// => { a: 999, b: 2, c: 3 }

I.setProp('a', undefined, { a: 1, b: 2, c: 3 })
// => { b: 2, c: 3 }

See also: modifyProp, removeProp


values

<T extends object>(object: T) => Array<T[keyof T & string]>

Return an array of the own enumerable property values of object

Example:

I.keys({ a: 1, b: 2, c: 3 })
// => [1, 2, 3]

See also: keys, entries


Function

binary

<T1, T2, R>(fn: VariadicFunction2<T1, T2, R>) => Function2<T1, T2, R>

Create a version of fn that accepts two arguments.

Note: The returned function is not curried.

Example:

const fn = (...args) => args
const wrapped = I.binary(fn)

fn(1, 2, 3)
// => [1, 2, 3]

wrapped(1, 2, 3)
// => [1, 2]

See also: unary


complement

<T extends VariadicFunction0<boolean>>(fn: T) => T

Create a version of a predicate fn that flips the returned boolean value.

Example:

const isZero = (v) => v === 0
const notZero = I.complement(isZero)

notZero(0)
// => false

notZero(1)
// => true

compose

<T extends unknown[], R>(fn: (args: ...T) => R) => (args: ...T) => R
<T extends unknown[], T1, R>(fn1: Function1<T1, R>, fn2: (args: ...T) => T1) => (args: ...T) => R
<T extends unknown[], T1, T2, R>(fn1: Function1<T2, R>, fn2: Function1<T1, T2>, fn3: (args: ...T) => T1) => (args: ...T) => R

Right-to-left function composition.

Note: This function is not curried.

Example:

const composed = I.compose(I.add(10), I.multiply(2))

composed(2)
// => 14

constant

<T>(value: T) => () => T

Create a function that always returns value.

Example:

I.map(I.constant(1), [1, 2, 3])
// => [1, 1, 1]

curry2

<T extends [unknown, unknown], R>(fn: (args: ...T) => R) => CurriedFunction2<T, R>

Create a curried version of a fn taking two arguments.

Example:

 const add = I.curry2((a, b) => a + b)

 add(1)(2)
 // => 3

 add(1, 2)
 // => 3

See also: curry3, curry4


curry3

<T extends [unknown, unknown, unknown], R>(fn: (args: ...T) => R) => CurriedFunction3<T, R>

Create a curried version of a fn taking three arguments.

Example:

 const add = I.curry3((a, b, c) => a + b + c)

 add(1)(2)(3)
 // => 6

 add(1, 2, 3)
 // => 6

See also: curry2, curry4


curry4

<T extends [unknown, unknown, unknown, unknown], R>(fn: (args: ...T) => R) => CurriedFunction4<T, R>

Create a curried version of a fn taking four arguments.

Example:

 const add = I.curry4((a, b, c, d) => a + b + c + d)

 add(1)(2)(3)(4)
 // => 10

 add(1, 2, 3, 4)
 // => 10

See also: curry2, curry3


flip

<T, U, R>(fn: Function2<T, U, R>) => Function2<U, T, R>

Flip the arguments of a binary function.

Note: The returned function is not curried.

Example:

const fn = (...args) => args
const flipped = I.flip(fn)

flipped(1, 2)
// => [2, 1]

identity

<T>(value: T) => T

Identity function. Returns the first argument.

Example:

I.identity(5)
// => 5

noop

() => undefined

Do nothing an return undefined.

Example:

I.map(I.noop, [1, 2, 3])
// => [undefined, undefined, undefined]

not

(bool: boolean) => boolean

Logical not. Flip the value of a boolean argument

Example:

I.not(true)
// => false

I.not(false)
// => true

See also: complement


pipe

<T>(initial: T) => T
<T, R>(initial: T, fn1: Function1<T, R>) => R
<T1, T2, R>(initial: T1, fn1: Function1<T1, T2>, fn2: Function1<T2, R>) => R

Pipe an initial value through one or more functions in left-to-right order, allowing the programmer to chain operations in a readable manner.

I.pipe(initial, f1, f2, ...fn) can be thought as syntax sugar for fn(...(f2(f1(initial))))

Note: This function is not curried.

Example:

I.pipe(
  [1, 2, 3],
  I.map((n) => n * 2),
  I.sum
)
// => 12

See also: compose


second

<T>(first: unknown, second: T) => T

Return the second argument.

Example:

I.second(1, 2)
// => 2

tap

<T>(fn: (value: T) => void) => (value: T) => T

Create a function that applies fn to its argument and returns the argument.

Useful for executing a side-effect within a pipeline.

Example:

I.pipe(
  [1, 2, 3],
  I.map(I.multiply(2)),
  I.filter(I.gt(2)),
  I.tap(console.log),
  I.sum
)
// Prints: [ 4, 6 ]
// => 10

unary

<T, R>(fn: VariadicFunction1<T, R>) => Function1<T, R>

Create a version of fn that accepts a single argument.

Example:

['1', '2', '3'].map(I.unary(parseInt))
// => [1, 2, 3]

See also: binary


Relation

clamp

<T extends Ordered>(interval: [lower: T, upper: T]) => (value: T) => T

Clamp a number within the closed interval [lower, upper].

Example:

I.clamp([0, 10], 5)
// => 5

I.clamp([0, 10], 15)
// => 10

I.clamp([0, 10], -5)
// => 0

equals

<T>(first: T) => (second: T) => boolean

Check if two values are deeply equal.

  • Primitive values are compared with SameValueZero.
  • Only the own enumerable keys of objects are considered.
  • The order of object keys does not matter.
  • Built-in objects (e.g. Arrays, Maps & Sets) are not checked for extra keys.
  • Sets and Map keys are compared with SameValueZero.
  • Error objects are equal if their name and message properties are equal.
  • Functions are compared with ===.
  • Supports cyclic references.
  • Does not support WeakMaps, WeakSets or typed arrays.

Example:

I.equals([1, 2, 3], [1, 2, 3])
// => true

I.equals([1, 2, 3], [4, 5, 6])
// => false

equalsBy

<T, U>(fn: (value: T) => U) => (first: T) => (second: T) => boolean

Like equals, but the function fn is applied to both values before determining equality.

Example:

I.equalsBy(Math.floor, 1, 1.5)
// => true

See also: equals


gt

<T extends Ordered>(first: T) => (second: T) => boolean

Check if the second argument is greater than the first.

Designed to be used as a curried predicate.

Example:

I.filter(I.gt(2), [1, 2, 3])
// => [3]

gte

<T extends Ordered>(first: T) => (second: T) => boolean

Check if the second argument is greater than or equal to the first.

Designed to be used as a curried predicate.

Example:

I.filter(I.gte(2), [1, 2, 3])
// => [2, 3]

lt

<T extends Ordered>(first: T) => (second: T) => boolean

Check if the second argument is less than the first.

Designed to be used as a curried predicate.

Example:

I.filter(I.lt(2), [1, 2, 3])
// => [1]

lte

<T extends Ordered>(first: T) => (second: T) => boolean

Check if the second argument is less than or equal to the first.

Designed to be used as a curried predicate.

Example:

I.filter(I.lte(2), [1, 2, 3])
// => [1, 2]

max

<T extends Ordered>(first: T) => (second: T) => T

Return the larger of two values.

Example:

I.max(1, 2)
// => 2

I.max('a', 'b')
// => 'b'

See also: min, maxBy


maxBy

<T, U extends Ordered>(fn: (value: T) => U) => (first: T, second: T) => T

Like max, but apply fn to both values before determining their ordering.

Example:

I.maxBy(Math.abs, 1, -2)
// => -2

See also: max, minBy


min

<T extends Ordered>(first: T) => (second: T) => T

Return the smaller of two values.

Example:

I.min(1, 2)
// => 1

I.min('a', 'b')
// => 'a'

See also: max, minBy


minBy

<T, U extends Ordered>(fn: (value: T) => U) => (first: T) => (second: T) => T

Like min, but apply fn to both values before determining their ordering.

Example:

I.minBy(Math.abs, -1, 2)
// => -1

See also: min, maxBy


Math

add

(n: number) => (m: number) => number

Add two numbers together.

Example:

I.map(I.add(1), [1, 2, 3])
// => [2, 3, 4]

dec

(n: number) => number

Decrement a number by 1.

Example:

I.map(I.dec, [1, 2, 3])
// => [0, 1, 2]

See also: inc


divideBy

(divisor: number) => (dividend: number) => number

Divide dividend by the divisor.

Example:

I.map(I.divideBy(2), [1, 2, 3])
// => [0.5, 1, 1.5]

inc

(n: number) => number

Increment a number by 1.

Example:

I.map(I.inc, [1, 2, 3])
// => [2, 3, 4]

multiply

(multiplicand: number) => (multiplier: number) => number

Multiply two numbers together.

Example:

I.map(I.multiply(2), [1, 2, 3])
// => [2, 4, 6]

negate

(n: number) => number

Return n with its sign reversed.

Example:

I.map(I.negate, [1, 2, 3])
// => [-1, -2, -3]

subtractBy

(subtrahend: number) => (minuend: number) => number

Subtract the subtrahend from the minuend.

Example:

I.map(I.subtractBy(1), [1, 2, 3])
// => [0, 1, 2]

Logic

maybe

<R>(defaultValue: R) => <T>(fn: (value: T) => R) => (maybeValue: undefined | T) => R

Apply fn to maybeValue if it is not undefined, return defaultValue otherwise.

Example:

I.maybe('', (s) => s.toUpperCase(), 'hi')
// => 'HI'

I.maybe('', (s) => s.toUpperCase(), undefined)
// => ''

See also: valueOr


valueOr

<T>(defaultValue: T) => (maybeValue: T | undefined) => T

Return maybeValue if it is not undefined, defaultValue otherwise.

Example:

I.valueOr(999, 0)
// => 0

I.valueOr(999, undefined)
// => 999

See also: maybe


String

capitalize

(string: string) => string

Convert the first code point of string to uppercase and the rest to lowercase.

Example:

I.capitalize('aBc')
// => 'Abc'

See also: toLowerCase, toUpperCase


split

(separator: RegExp | string) => (string: string) => string

Split the string into an array of substrings between each separator.

Example:

I.split(', ', 'a, b, c')
// => ['a', 'b', 'c']

See also: join


test

(regexp: RegExp) => (string: string) => boolean

Check if string matches the regexp.

Example:

I.test(/abc/, 'abc')
// => true

toLowerCase

(string: string) => string

Convert string to lowercase.

Example:

I.toLowerCase('ABC')
// => 'abc'

See also: toUpperCase, capitalize


toUpperCase

(string: string) => string

Convert string to uppercase.

Example:

I.toUpperCase('abc')
// => 'ABC'

See also: toLowerCase, capitalize


trim

(string: string) => string

Remove whitespace from both ends of a string.

Example:

I.trim('  abc  ')
// => 'abc'

See also: trimStart, trimEnd


trimEnd

(string: string) => string

Remove whitespace from the end of a string.

Example:

I.trimEnd('  abc  ')
// => '  abc'

See also: trimStart, trim


trimStart

(string: string) => string

Remove whitespace from the beginning of a string.

Example:

I.trimStart('  abc  ')
// => 'abc  '

See also: trimEnd, trim


Type tests

isArray

<T>(value: T | unknown[]) => value is unknown[]

Check if the value is an Array.


isBigInt

<T>(value: T | bigint) => value is bigint

Check if the value is a BigInt.


isBoolean

<T>(value: T | boolean) => value is boolean

Check if the value is a boolean.


isDate

<T>(value: T | Date) => value is Date

Check if the value is a Date.


isDefined

<T>(value: T | undefined) => value is T

Check if the value is not undefined.


isError

<T>(value: T | Error) => value is Error

Check if the value is an Error.


isFunction

<T>(value: T | Function) => value is Function

Check if the value is a function.


isMap

<T>(value: T | Map<unknown, unknown>) => value is Map<unknown, unknown>

Check if the value is a Map.


isNil

<T>(value: T | null | undefined) => value is undefined | null

Check if the value is null or undefined.


isNull

<T>(value: T | null) => value is null

Check if the value is null.


isNumber

<T>(value: T | number) => value is number

Check if the value is a number.


isObject

<T>(value: T | object) => value is object

Check if the value is an object.

Note that functions and arrays are also objects.


isRegExp

<T>(value: T | RegExp) => value is RegExp

Check if the value is a RegExp.


isSet

<T>(value: T | Set<unknown>) => value is Set<unknown>

Check if the value is a Set.


isString

<T>(value: T | string) => value is string

Check if the value is a string.


isSymbol

<T>(value: T | symbol) => value is symbol

Check if the value is a Symbol.


isUndefined

<T>(value: T | undefined) => value is undefined

Check if the value is undefined.