@algosail/array
v0.1.0
Published
Small collection of FP utilities for working with arrays.
Readme
@algosail/array
Array constructors, getters, and transformers. All functions are curried. Source arrays are never mutated.
Contents
Constructors: of · empty · range · unfold · chainRec
Getters: lookup · head · last · tail · init · size · array · find · findMap
Predicates: all · any · none · elem · equals · lte · isOutOfBounds
Transformers: map · filter · reject · flatmap · ap · concat · alt · append · prepend · reverse · sort · sortBy · take · drop · takeLast · dropLast · takeWhile · dropWhile · zip · zipWith · intercalate · groupBy · extend · joinWith
Folds: reduce · reduceC · foldMap · traverse
Constructors
of
of :: a -> Array aLifts a value into a singleton array.
of(1) // => [1]
of([1, 2]) // => [[1, 2]]
of(undefined) // => [undefined]empty
empty :: () -> Array aReturns a new empty array.
empty() // => []range
range :: Integer -> Integer -> Array IntegerProduces [from, from+1, ..., to-1]. The upper bound is exclusive.
range(0)(5) // => [0, 1, 2, 3, 4]
range(2)(6) // => [2, 3, 4, 5]
range(3)(3) // => []
range(5)(3) // => []unfold
unfold :: (b -> Maybe [a, b]) -> b -> Array aGenerates an array from a seed. The stepper returns just([value, nextSeed]) to continue, or nothing() to stop.
// Fibonacci sequence up to 100
unfold(([a, b]) => (b > 100 ? nothing() : just([a, [b, a + b]])))([0, 1])
// => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
// Repeat a value N times
unfold((n) => (n <= 0 ? nothing() : just(['x', n - 1])))(3)
// => ['x', 'x', 'x']chainRec
chainRec :: ((next, done, a) -> Array Step) -> a -> Array bStack-safe recursive array expansion. Each call to f may return multiple next or done steps.
// Binary tree expansion
chainRec((next, done, n) => (n <= 0 ? [done(0)] : [next(n - 1), next(n - 1)]))(
3,
)
// => [0, 0, 0, 0, 0, 0, 0, 0] (2^3 leaves)Getters
lookup
lookup :: Number -> Array a -> Maybe aSafe index access — Just the element, or Nothing if out of bounds.
lookup(0)([10, 20, 30]) // => just(10)
lookup(2)([10, 20, 30]) // => just(30)
lookup(3)([10, 20, 30]) // => nothing()
lookup(-1)([10, 20, 30]) // => nothing()head
head :: Array a -> Maybe aFirst element, or Nothing for empty arrays.
head([1, 2, 3]) // => just(1)
head([]) // => nothing()last
last :: Array a -> Maybe aLast element, or Nothing for empty arrays.
last([1, 2, 3]) // => just(3)
last([]) // => nothing()tail
tail :: Array a -> Maybe (Array a)All elements after the first, or Nothing for empty arrays.
tail([1, 2, 3]) // => just([2, 3])
tail([1]) // => just([])
tail([]) // => nothing()init
init :: Array a -> Maybe (Array a)All elements except the last, or Nothing for empty arrays.
init([1, 2, 3]) // => just([1, 2])
init([1]) // => just([])
init([]) // => nothing()size
size :: Array a -> IntegerReturns the number of elements.
size([1, 2, 3]) // => 3
size([]) // => 0array
array :: b -> (a -> Array a -> b) -> Array a -> bCase analysis: returns the empty value for [], or applies nonEmpty(head)(tail) for non-empty.
array('empty')((h) => (t) => `head=${h}, tail=${t}`)([5, 6, 7]) // => 'head=5, tail=6,7'
array('empty')((h) => (t) => h)([]) // => 'empty'find
find :: (a -> Boolean) -> Array a -> Maybe aReturns Just the first matching element, or Nothing.
find((x) => x > 2)([1, 2, 3, 4]) // => just(3)
find((x) => x > 10)([1, 2, 3]) // => nothing()findMap
findMap :: (a -> Maybe b) -> Array a -> Maybe bReturns the first Just result from applying f to each element.
findMap((x) => (x > 2 ? just(x * 10) : nothing()))([1, 2, 3, 4]) // => just(30)
findMap((x) => (x > 10 ? just(x) : nothing()))([1, 2, 3]) // => nothing()Predicates
all
all :: (a -> Boolean) -> Array a -> BooleanTrue when every element satisfies the predicate.
all((x) => x > 0)([1, 2, 3]) // => true
all((x) => x > 0)([-1, 2, 3]) // => false
all((x) => x > 0)([]) // => true (vacuously)any
any :: (a -> Boolean) -> Array a -> BooleanTrue when at least one element satisfies the predicate.
any((x) => x > 2)([1, 2, 3]) // => true
any((x) => x > 5)([1, 2, 3]) // => falsenone
none :: (a -> Boolean) -> Array a -> BooleanTrue when no element satisfies the predicate.
none((x) => x > 5)([1, 2, 3]) // => true
none((x) => x > 2)([1, 2, 3]) // => falseelem
elem :: ((a, a) -> Boolean) -> a -> Array a -> BooleanTrue when x is found using the comparator.
elem((a, b) => a === b)(2)([1, 2, 3]) // => true
elem((a, b) => a === b)(5)([1, 2, 3]) // => falseequals
equals :: ((a, a) -> Boolean) -> Array a -> Array a -> BooleanElement-wise equality using an explicit comparator.
equals((a, b) => a === b)([1, 2])([1, 2]) // => true
equals((a, b) => a === b)([1, 2])([1, 3]) // => false
equals((a, b) => a === b)([1, 2])([1]) // => false (lengths differ)lte
lte :: ((a, a) -> Boolean) -> Array a -> Array a -> BooleanLexicographic ordering using an explicit lte comparator for elements.
lte((a, b) => a <= b)([1, 2])([1, 3]) // => true
lte((a, b) => a <= b)([1, 3])([1, 2]) // => false
lte((a, b) => a <= b)([1])([1, 2]) // => true (prefix is ≤)isOutOfBounds
isOutOfBounds :: Number -> Array a -> BooleanTrue when the index is negative or >= length.
isOutOfBounds(0, [1, 2, 3]) // => false
isOutOfBounds(3, [1, 2, 3]) // => true
isOutOfBounds(-1, [1, 2, 3]) // => trueTransformers
map
map :: (a -> b) -> Array a -> Array bmap((x) => x * 2)([1, 2, 3]) // => [2, 4, 6]
map((s) => s.length)(['a', 'bb']) // => [1, 2]filter
filter :: (a -> Boolean) -> Array a -> Array afilter((x) => x > 2)([1, 2, 3, 4]) // => [3, 4]
filter((x) => x % 2 === 0)([1, 2, 3, 4]) // => [2, 4]reject
reject :: (a -> Boolean) -> Array a -> Array aComplement of filter — keeps elements that do NOT satisfy the predicate.
reject((x) => x > 2)([1, 2, 3, 4]) // => [1, 2]flatmap
flatmap :: (a -> Array b) -> Array a -> Array bMaps then flattens one level.
flatmap((x) => [x, x * 2])([1, 2, 3]) // => [1, 2, 2, 4, 3, 6]
flatmap((x) => (x > 1 ? [x] : []))([1, 2, 3]) // => [2, 3]ap
ap :: Array (a -> b) -> Array a -> Array bCartesian application — every function applied to every value.
ap([(x) => x + 1, (x) => x * 2])([10, 20]) // => [11, 21, 20, 40]concat
concat :: Array a -> Array a -> Array aconcat([1, 2])([3, 4]) // => [1, 2, 3, 4]append / prepend
append :: a -> Array a -> Array a
prepend :: a -> Array a -> Array aappend(4)([1, 2, 3]) // => [1, 2, 3, 4]
prepend(0)([1, 2, 3]) // => [0, 1, 2, 3]reverse
reverse :: Array a -> Array aReturns a reversed copy; the original is not mutated.
reverse([1, 2, 3]) // => [3, 2, 1]sort
sort :: ((a, a) -> Boolean) -> Array a -> Array aStable sort with an explicit lte comparator.
sort((a, b) => a <= b)([3, 1, 2]) // => [1, 2, 3]
sort((a, b) => a >= b)([3, 1, 2]) // => [3, 2, 1]sortBy
sortBy :: ((b, b) -> Boolean) -> (a -> b) -> Array a -> Array aStable sort using a key extractor.
sortBy((a, b) => a <= b)((x) => x.age)([{ age: 30 }, { age: 20 }, { age: 25 }])
// => [{ age: 20 }, { age: 25 }, { age: 30 }]take / drop / takeLast / dropLast
take :: Integer -> Array a -> Maybe (Array a)
drop :: Integer -> Array a -> Maybe (Array a)
takeLast :: Integer -> Array a -> Maybe (Array a)
dropLast :: Integer -> Array a -> Maybe (Array a)All return Nothing for negative n or n > length.
take(2)([1, 2, 3]) // => just([1, 2])
drop(1)([1, 2, 3]) // => just([2, 3])
takeLast(2)([1, 2, 3]) // => just([2, 3])
dropLast(1)([1, 2, 3]) // => just([1, 2])
take(5)([1, 2]) // => nothing()takeWhile / dropWhile
takeWhile :: (a -> Boolean) -> Array a -> Array a
dropWhile :: (a -> Boolean) -> Array a -> Array atakeWhile((x) => x < 3)([1, 2, 3, 4]) // => [1, 2]
dropWhile((x) => x < 3)([1, 2, 3, 4]) // => [3, 4]zip / zipWith
zip :: Array a -> Array b -> Array [a, b]
zipWith :: (a -> b -> c) -> Array a -> Array b -> Array cTruncate to the shorter array.
zip([1, 2, 3])(['a', 'b']) // => [[1,'a'], [2,'b']]
zipWith((a) => (b) => a + b)([1, 2, 3])([10, 20]) // => [11, 22]intercalate
intercalate :: Array a -> Array (Array a) -> Array aInserts a separator between sub-arrays and flattens.
intercalate([0])([[1, 2], [3, 4], [5]]) // => [1, 2, 0, 3, 4, 0, 5]
intercalate([','])([['a'], ['b'], ['c']]) // => ['a', ',', 'b', ',', 'c']groupBy
groupBy :: (a -> a -> Boolean) -> Array a -> Array (Array a)Groups consecutive equal elements.
groupBy((a) => (b) => a === b)([1, 1, 2, 3, 3, 3]) // => [[1,1], [2], [3,3,3]]
groupBy((a) => (b) => Math.floor(a) === Math.floor(b))([1.1, 1.9, 2.0, 3.5])
// => [[1.1, 1.9], [2.0], [3.5]]extend
extend :: (Array a -> b) -> Array a -> Array bCobind — applies f to each suffix: [f(xs), f(tail(xs)), ..., f([last])].
extend(head)([1, 2, 3]) // => [just(1), just(2), just(3)]
extend(size)([1, 2, 3]) // => [3, 2, 1]joinWith
joinWith :: String -> Array String -> StringjoinWith(', ')(['a', 'b', 'c']) // => 'a, b, c'
joinWith('')(['h', 'i']) // => 'hi'Folds
reduce
reduce :: ((b, a) -> b) -> b -> Array a -> bLeft fold with an uncurried binary function.
reduce((acc, x) => acc + x)(0)([1, 2, 3]) // => 6reduceC
reduceC :: (b -> a -> b) -> b -> Array a -> bLeft fold with a curried binary function.
reduceC((acc) => (x) => acc + x)(0)([1, 2, 3]) // => 6foldMap
foldMap :: ((b, b) -> b) -> b -> (a -> b) -> Array a -> bMaps elements into a monoid and concatenates.
foldMap((a, b) => a + b)(0)((x) => x * 2)([1, 2, 3]) // => 12
foldMap((a, b) => a + b)('')(String)([1, 2, 3]) // => '123'traverse
traverse :: (b -> f b) -> (f (a->b) -> f a -> f b) -> ((a->b) -> f a -> f b) -> (a -> f b) -> Array a -> f (Array b)Applicative traversal — passes explicit apOf, apAp, apMap to work with any applicative.
// Traverse with Maybe — short-circuits on Nothing
const safeDouble = (x) => (x < 0 ? nothing() : just(x * 2))
traverse(just)((mf) => (ma) => ap(mf)(ma))((f) => (ma) => map(f)(ma))(
safeDouble,
)([1, 2, 3]) // => just([2, 4, 6])
traverse(just)((mf) => (ma) => ap(mf)(ma))((f) => (ma) => map(f)(ma))(
safeDouble,
)([1, -1, 3]) // => nothing()