iiris v0.1.1
Iiris 👁️
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
- Getting Started
- Why Iiris?
- API Reference
- Basic array operations
- Transforming arrays
- Reducing arrays
- Searching arrays with a predicate
- Searching arrays by value
- Grouping arrays by key
- Building arrays
- Slicing arrays
- Sorting arrays
- Zipping arrays
- Set operations
- Object
- Function
- Relation
- Math
- Logic
- String
- Type tests - isArray - isBigInt - isBoolean - isDate - isDefined - isError - isFunction - isMap - isNil - isNull - isNumber - isObject - isRegExp - isSet - isString - isSymbol - isUndefined
Installation
Run either
$ npm install iirisor
$ yarn add iirisdepending 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
) // => 233168Why 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.mapoperates only on arrays, whileR.mapsupports 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]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]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 | undefinedReturn the first element of the array or undefined.
Example:
I.head([1, 2, 3])
// => 1
I.head([])
// => undefinedinit
<T>(array: T[]) => T[]Return all elements of the array except the last.
Example:
I.init([1, 2, 3])
// => [1, 2]
I.init([])
// => []isEmpty
<T>(array: T[]) => booleanCheck if array is empty.
Example:
I.isEmpty([1, 2, 3])
// => false
I.isEmpty([])
// => trueSee also: length
last
<T>(array: T[]) => T | undefinedReturn the last element of the array or undefined.
Example:
I.last([1, 2, 3])
// => 3
I.last([])
// => undefinedlength
<T>(array: T[]) => numberReturn the length of an array.
Example:
I.length([1, 2, 3])
// => 3
I.length([])
// => 0See 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
indexis not withinarraybounds, thearrayis returned unchanged. - Removes the element if
fnreturnsundefined.
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]nth
(index: number) => <T>(array: T[]) => T | undefinedReturn the nth element from array or undefined.
Example:
I.nth(0, [1, 2, 3])
// => 1
I.nth(0, [])
// => undefinednthEquals
(index: number) => <T>(value: T) => (array: T[]) => booleanCheck if the nth element of array equals value, using equals for
determining equality.
Example:
I.nthEquals(0, 'a', ['a', 'b', 'c'])
// => trueSee also: nthSatisfies
nthOr
<T>(defaultValue: T) => (index: number) => (array: T[]) => TLike 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])
// => 999nthSatisfies
(index: number) => <T>(predicate: (value: T) => boolean) => (array: T[]) => booleanCheck if the nth element of array satisfies the predicate.
Example:
I.nthSatisfies(0, I.gt(0), [1, 2, 3])
// => trueSee 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]removeNth
(index: number) => <T>(array: T[]) => T[]Returns a copy of array without the nth element.
- If
indexis not within thearraybounds, thearrayis 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]setNth
(index: number) => <T>(value: undefined | T) => (array: T[]) => T[]Returns a copy of array where nth element has been replaced with value.
- If
indexis not within thearraybounds, thearrayis returned unchanged. - Removes the element if
valueisundefined.
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([])
// => []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]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[]) => stringConvert 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 | undefinedReturn the largest element of an array or undefined.
Example:
I.maximum([1, 2, 3])
// => 3
I.maximum([])
// => undefinedmaximumBy
<T, U extends Ordered>(fn: (value: T) => U) => (array: T[]) => T | undefinedLike 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 }minimum
<T extends Ordered>(array: T[]) => T | undefinedReturn the smallest element of array or undefined.
Example:
I.minimum([1, 2, 3])
// => 1
I.minimum([])
// => undefinedminimumBy
<T, U extends Ordered>(fn: (value: T) => U) => (array: T[]) => T | undefinedLike 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 }reduce
<T, R>(reducer: (accumulator: R, value: T) => R) => (initial: R) => (array: T[]) => RLeft-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
// => 10See also: reduceRight
reduceRight
<T, R>(reducer: (value: T, accumulator: R) => R) => (initial: R) => (array: T[]) => RRight-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))
// => 10See also: reduce
sum
(numbers: number[]) => numberSum 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.9999999999999999See also: sumBy
sumBy
<T>(fn: (value: T) => number) => (array: T[]) => numberLike 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 }])
// => 30See also: sum
Searching arrays with a predicate
count
<T>(predicate: (value: T) => boolean) => (array: T[]) => numberCount the number of elements in the array the satisfy the predicate.
Example:
I.count((n) => n > 1, [1, 2, 3])
// => 2See also: filter
every
<T>(predicate: (value: T) => boolean) => (array: T[]) => booleanCheck 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])
// => falsefilter
<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 | undefinedFind 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'])
// => undefinedfindIndex
<T>(predicate: (value: T) => boolean) => (array: T[]) => numberFind 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'])
// => -1See also: findLastIndex, find
findLast
<T>(predicate: (value: T) => boolean) => (array: T[]) => T | undefined
<T, U>(guard: (value: T) => value is U) => (array: T[]) => U | undefinedFind 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'])
// => undefinedSee also: find, findLastIndex
findLastIndex
<T>(predicate: (value: T) => boolean) => (array: T[]) => numberFind 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'])
// => -1none
<T>(predicate: (value: T) => boolean) => (array: T[]) => booleanCheck 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])
// => falsepartition
<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[]) => booleanCheck 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])
// falseSearching arrays by value
includes
<T>(value: T) => (array: T[]) => booleanCheck 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])
// => falseindexOf
<T>(value: T) => (array: T[]) => numberReturn 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'])
// => -1See also: lastIndexOf, includes
lastIndexOf
<T>(value: T) => (array: T[]) => numberReturn 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'])
// => -1Grouping 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 }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)
// => []repeat
<T>(value: T) => (n: number) => T[]Repeat the given value n times.
Example:
I.repeat('a', 5)
// => ['a', 'a', 'a', 'a', 'a']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]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]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]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]takeLast
<T>(n: number) => (array: T[]) => T[]Take the last n elements of an array.
Example:
I.takeLast(2, [1, 2, 3])
// => [2, 3]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) => numberGiven 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) => numberGiven 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 }]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]]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 })
// => falsekeys
<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']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 & UCopy 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) => TReturn a copy of object where the property key has replaced by applying
fn to its current value.
- If
keyis not an own property ofobject, theobjectis returned unchanged. - If
fnreturnsundefined, 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', {})
// => undefinedpropEquals
<K extends string>(key: K) => <V>(value: V) => <T extends HasKey<K, V>>(object: T) => booleanCheck 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)
// => trueSee 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 })
// => 999propSatisfies
<K extends string>(key: K) => <V>(predicate: (value: V) => boolean) => <T extends HasKey<K, V>>(object: T) => booleanCheck if property key of object satisfies the predicate.
Example:
const users = [
{ name: 'Alice' },
{ name: 'Bob' },
]
I.some(I.propSatisfies('name', I.test(/^A/)), users)
// => trueSee 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
keyis not an own property ofobject, theobjectis 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) => TReturn a copy of object with property key set to value.
- If
valueisundefined, 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]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) => TCreate 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)
// => truecompose
<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) => RRight-to-left function composition.
Note: This function is not curried.
Example:
const composed = I.compose(I.add(10), I.multiply(2))
composed(2)
// => 14constant
<T>(value: T) => () => TCreate 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)
// => 3curry3
<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)
// => 6curry4
<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)
// => 10flip
<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) => TIdentity function. Returns the first argument.
Example:
I.identity(5)
// => 5noop
() => undefinedDo nothing an return undefined.
Example:
I.map(I.noop, [1, 2, 3])
// => [undefined, undefined, undefined]not
(bool: boolean) => booleanLogical not. Flip the value of a boolean argument
Example:
I.not(true)
// => false
I.not(false)
// => trueSee 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>) => RPipe 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
)
// => 12See also: compose
second
<T>(first: unknown, second: T) => TReturn the second argument.
Example:
I.second(1, 2)
// => 2tap
<T>(fn: (value: T) => void) => (value: T) => TCreate 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 ]
// => 10unary
<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) => TClamp 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)
// => 0equals
<T>(first: T) => (second: T) => booleanCheck 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
nameandmessageproperties 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])
// => falseequalsBy
<T, U>(fn: (value: T) => U) => (first: T) => (second: T) => booleanLike equals, but the function fn is applied to both values before
determining equality.
Example:
I.equalsBy(Math.floor, 1, 1.5)
// => trueSee also: equals
gt
<T extends Ordered>(first: T) => (second: T) => booleanCheck 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) => booleanCheck 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) => booleanCheck 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) => booleanCheck 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) => TReturn the larger of two values.
Example:
I.max(1, 2)
// => 2
I.max('a', 'b')
// => 'b'maxBy
<T, U extends Ordered>(fn: (value: T) => U) => (first: T, second: T) => TLike max, but apply fn to both values before determining their
ordering.
Example:
I.maxBy(Math.abs, 1, -2)
// => -2min
<T extends Ordered>(first: T) => (second: T) => TReturn the smaller of two values.
Example:
I.min(1, 2)
// => 1
I.min('a', 'b')
// => 'a'minBy
<T, U extends Ordered>(fn: (value: T) => U) => (first: T) => (second: T) => TLike min, but apply fn to both values before determining their
ordering.
Example:
I.minBy(Math.abs, -1, 2)
// => -1Math
add
(n: number) => (m: number) => numberAdd two numbers together.
Example:
I.map(I.add(1), [1, 2, 3])
// => [2, 3, 4]dec
(n: number) => numberDecrement a number by 1.
Example:
I.map(I.dec, [1, 2, 3])
// => [0, 1, 2]See also: inc
divideBy
(divisor: number) => (dividend: number) => numberDivide dividend by the divisor.
Example:
I.map(I.divideBy(2), [1, 2, 3])
// => [0.5, 1, 1.5]inc
(n: number) => numberIncrement a number by 1.
Example:
I.map(I.inc, [1, 2, 3])
// => [2, 3, 4]multiply
(multiplicand: number) => (multiplier: number) => numberMultiply two numbers together.
Example:
I.map(I.multiply(2), [1, 2, 3])
// => [2, 4, 6]negate
(n: number) => numberReturn n with its sign reversed.
Example:
I.map(I.negate, [1, 2, 3])
// => [-1, -2, -3]subtractBy
(subtrahend: number) => (minuend: number) => numberSubtract 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) => RApply 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) => TReturn maybeValue if it is not undefined, defaultValue otherwise.
Example:
I.valueOr(999, 0)
// => 0
I.valueOr(999, undefined)
// => 999See also: maybe
String
capitalize
(string: string) => stringConvert 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) => stringSplit 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) => booleanCheck if string matches the regexp.
Example:
I.test(/abc/, 'abc')
// => truetoLowerCase
(string: string) => stringConvert string to lowercase.
Example:
I.toLowerCase('ABC')
// => 'abc'See also: toUpperCase, capitalize
toUpperCase
(string: string) => stringConvert string to uppercase.
Example:
I.toUpperCase('abc')
// => 'ABC'See also: toLowerCase, capitalize
trim
(string: string) => stringRemove whitespace from both ends of a string.
Example:
I.trim(' abc ')
// => 'abc'trimEnd
(string: string) => stringRemove whitespace from the end of a string.
Example:
I.trimEnd(' abc ')
// => ' abc'trimStart
(string: string) => stringRemove whitespace from the beginning of a string.
Example:
I.trimStart(' abc ')
// => 'abc 'Type tests
isArray
<T>(value: T | unknown[]) => value is unknown[]Check if the value is an
Array.
isBigInt
<T>(value: T | bigint) => value is bigintCheck if the value is a
BigInt.
isBoolean
<T>(value: T | boolean) => value is booleanCheck if the value is a
boolean.
isDate
<T>(value: T | Date) => value is DateCheck if the value is a
Date.
isDefined
<T>(value: T | undefined) => value is TCheck if the value is not
undefined.
isError
<T>(value: T | Error) => value is ErrorCheck if the value is an
Error.
isFunction
<T>(value: T | Function) => value is FunctionCheck 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 | nullCheck if the value is
null or
undefined.
isNull
<T>(value: T | null) => value is nullCheck if the value is
null.
isNumber
<T>(value: T | number) => value is numberCheck if the value is a
number.
isObject
<T>(value: T | object) => value is objectCheck if the value is an
object.
Note that functions and arrays are also objects.
isRegExp
<T>(value: T | RegExp) => value is RegExpCheck 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 stringCheck if the value is a
string.
isSymbol
<T>(value: T | symbol) => value is symbolCheck if the value is a
Symbol.
isUndefined
<T>(value: T | undefined) => value is undefinedCheck if the value is
undefined.