iiris
v0.1.1
Published
A functional JavaScript utility library
Downloads
26
Maintainers
Readme
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
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, whileR.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]
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 | undefined
Return the first element of the array
or undefined
.
Example:
I.head([1, 2, 3])
// => 1
I.head([])
// => undefined
init
<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[]) => 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
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 withinarray
bounds, thearray
is returned unchanged. - Removes the element if
fn
returnsundefined
.
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 | undefined
Return the nth element from array
or undefined
.
Example:
I.nth(0, [1, 2, 3])
// => 1
I.nth(0, [])
// => undefined
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
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]
removeNth
(index: number) => <T>(array: T[]) => T[]
Returns a copy of array
without the nth element.
- If
index
is not within thearray
bounds, thearray
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]
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 thearray
bounds, thearray
is returned unchanged. - Removes the element if
value
isundefined
.
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[]) => 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
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 }
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
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 }
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
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
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
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
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
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
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 }
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) => 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 }]
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 })
// => 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']
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 ofobject
, theobject
is returned unchanged. - If
fn
returnsundefined
, 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
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
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 ofobject
, theobject
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
isundefined
, 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) => 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
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
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
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
andmessage
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'
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
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'
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
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'
trimEnd
(string: string) => string
Remove whitespace from the end of a string
.
Example:
I.trimEnd(' abc ')
// => ' abc'
trimStart
(string: string) => string
Remove 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 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
.