From e313a01b7e80f6cb7704055a190e5623c9d22c6d Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Wed, 22 May 2024 14:30:14 +0200 Subject: [PATCH] =?UTF-8?q?Array:=20fix=20`flatMapNullable`=20implementati?= =?UTF-8?q?on=20and=20add=20descriptions=20/=20ex=E2=80=A6=20(#2808)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/rich-schools-sell.md | 5 + packages/effect/src/Array.ts | 794 ++++++++++++++++++++++++++++- packages/effect/test/Array.test.ts | 2 + 3 files changed, 784 insertions(+), 17 deletions(-) create mode 100644 .changeset/rich-schools-sell.md diff --git a/.changeset/rich-schools-sell.md b/.changeset/rich-schools-sell.md new file mode 100644 index 0000000000..67f504f537 --- /dev/null +++ b/.changeset/rich-schools-sell.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Array: fix `flatMapNullable` implementation and add descriptions / examples diff --git a/packages/effect/src/Array.ts b/packages/effect/src/Array.ts index b3085ffa5a..e7f2ed90d3 100644 --- a/packages/effect/src/Array.ts +++ b/packages/effect/src/Array.ts @@ -46,6 +46,12 @@ export type NonEmptyArray = [A, ...Array] /** * Builds a `NonEmptyArray` from an non-empty collection of elements. * + * @example + * import { Array } from "effect" + * + * const result = Array.make(1, 2, 3) + * assert.deepStrictEqual(result, [1, 2, 3]) + * * @category constructors * @since 2.0.0 */ @@ -56,6 +62,12 @@ export const make = >( /** * Creates a new `Array` of the specified length. * + * @example + * import { Array } from "effect" + * + * const result = Array.allocate(3) + * assert.deepStrictEqual(result.length, 3) + * * @category constructors * @since 2.0.0 */ @@ -103,9 +115,9 @@ export const range = (start: number, end: number): NonEmptyArray => * **Note**. `n` is normalized to an integer >= 1. * * @example - * import { replicate } from "effect/Array" + * import { Array } from "effect" * - * assert.deepStrictEqual(replicate("a", 3), ["a", "a", "a"]) + * assert.deepStrictEqual(Array.replicate("a", 3), ["a", "a", "a"]) * * @category constructors * @since 2.0.0 @@ -117,6 +129,15 @@ export const replicate: { /** * Creates a new `Array` from an iterable collection of values. + * If the input is already an array, it returns the input as-is. + * Otherwise, it converts the iterable collection to an array. + * + * @example + * import { Array } from "effect" + * + * const set = new Set([1, 2, 3]) + * const result = Array.fromIterable(set) + * assert.deepStrictEqual(result, [1, 2, 3]) * * @category constructors * @since 2.0.0 @@ -130,10 +151,10 @@ export const fromIterable = (collection: Iterable): Array => * @param self - The record to transform. * * @example - * import { fromRecord } from "effect/Array" + * import { Array } from "effect" * * const x = { a: 1, b: 2, c: 3 } - * assert.deepStrictEqual(fromRecord(x), [["a", 1], ["b", 2], ["c", 3]]) + * assert.deepStrictEqual(Array.fromRecord(x), [["a", 1], ["b", 2], ["c", 3]]) * * @category conversions * @since 2.0.0 @@ -141,12 +162,32 @@ export const fromIterable = (collection: Iterable): Array => export const fromRecord: (self: Readonly>) => Array<[K, A]> = Record.toEntries /** + * Converts an `Option` to an array. + * + * @example + * import { Array, Option } from "effect" + * + * assert.deepStrictEqual(Array.fromOption(Option.some(1)), [1]) + * assert.deepStrictEqual(Array.fromOption(Option.none()), []) + * * @category conversions * @since 2.0.0 */ export const fromOption: (self: Option) => Array = O.toArray /** + * Matches the elements of an array, applying functions to cases of empty and non-empty arrays. + * + * @example + * import { Array } from "effect" + * + * const match = Array.match({ + * onEmpty: () => "empty", + * onNonEmpty: ([head, ...tail]) => `head: ${head}, tail: ${tail.length}` + * }) + * assert.deepStrictEqual(match([]), "empty") + * assert.deepStrictEqual(match([1, 2, 3]), "head: 1, tail: 2") + * * @category pattern matching * @since 2.0.0 */ @@ -173,6 +214,18 @@ export const match: { ): B | C => isNonEmptyReadonlyArray(self) ? onNonEmpty(self) : onEmpty()) /** + * Matches the elements of an array from the left, applying functions to cases of empty and non-empty arrays. + * + * @example + * import { Array } from "effect" + * + * const matchLeft = Array.matchLeft({ + * onEmpty: () => "empty", + * onNonEmpty: (head, tail) => `head: ${head}, tail: ${tail.length}` + * }) + * assert.deepStrictEqual(matchLeft([]), "empty") + * assert.deepStrictEqual(matchLeft([1, 2, 3]), "head: 1, tail: 2") + * * @category pattern matching * @since 2.0.0 */ @@ -199,6 +252,18 @@ export const matchLeft: { ): B | C => isNonEmptyReadonlyArray(self) ? onNonEmpty(headNonEmpty(self), tailNonEmpty(self)) : onEmpty()) /** + * Matches the elements of an array from the right, applying functions to cases of empty and non-empty arrays. + * + * @example + * import { Array } from "effect" + * + * const matchRight = Array.matchRight({ + * onEmpty: () => "empty", + * onNonEmpty: (init, last) => `init: ${init.length}, last: ${last}` + * }) + * assert.deepStrictEqual(matchRight([]), "empty") + * assert.deepStrictEqual(matchRight([1, 2, 3]), "init: 2, last: 3") + * * @category pattern matching * @since 2.0.0 */ @@ -230,6 +295,13 @@ export const matchRight: { /** * Prepend an element to the front of an `Iterable`, creating a new `NonEmptyArray`. * + * @example + * import { Array } from "effect" + * + * const original = [2, 3, 4]; + * const result = Array.prepend(original, 1); + * assert.deepStrictEqual(result, [1, 2, 3, 4]); + * * @category concatenating * @since 2.0.0 */ @@ -245,10 +317,10 @@ export const prepend: { * @example * import { Array } from "effect" * - * assert.deepStrictEqual( - * Array.prependAll([1, 2], ["a", "b"]), - * ["a", "b", 1, 2] - * ) + * const prefix = [0, 1]; + * const array = [2, 3]; + * const result = Array.prependAll(array, prefix); + * assert.deepStrictEqual(result, [0, 1, 2, 3]); * * @category concatenating * @since 2.0.0 @@ -268,6 +340,13 @@ export const prependAll: { /** * Append an element to the end of an `Iterable`, creating a new `NonEmptyArray`. * + * @example + * import { Array } from "effect" + * + * const original = [1, 2, 3]; + * const result = Array.append(original, 4); + * assert.deepStrictEqual(result, [1, 2, 3, 4]); + * * @category concatenating * @since 2.0.0 */ @@ -296,7 +375,22 @@ export const appendAll: { ) /** - * Reduce an `Iterable` from the left, keeping all intermediate results instead of only the final result. + * Accumulates values from an `Iterable` starting from the left, storing + * each intermediate result in an array. Useful for tracking the progression of + * a value through a series of transformations. + * + * @example + * import { Array } from "effect"; + * + * const numbers = [1, 2, 3, 4] + * const result = Array.scan(numbers, 0, (acc, value) => acc + value) + * assert.deepStrictEqual(result, [0, 1, 3, 6, 10]) + * + * // Explanation: + * // This function starts with the initial value (0 in this case) + * // and adds each element of the array to this accumulator one by one, + * // keeping track of the cumulative sum after each addition. + * // Each of these sums is captured in the resulting array. * * @category folding * @since 2.0.0 @@ -315,7 +409,16 @@ export const scan: { }) /** - * Reduce an `Iterable` from the right, keeping all intermediate results instead of only the final result. + * Accumulates values from an `Iterable` starting from the right, storing + * each intermediate result in an array. Useful for tracking the progression of + * a value through a series of transformations. + * + * @example + * import { Array } from "effect"; + * + * const numbers = [1, 2, 3, 4] + * const result = Array.scanRight(numbers, 0, (acc, value) => acc + value) + * assert.deepStrictEqual(result, [10, 9, 7, 4, 0]) * * @category folding * @since 2.0.0 @@ -467,6 +570,12 @@ export const unsafeGet: { /** * Return a tuple containing the first element, and a new `Array` of the remaining elements, if any. * + * @example + * import { Array } from "effect"; + * + * const result = Array.unprepend([1, 2, 3, 4]) + * assert.deepStrictEqual(result, [1, [2, 3, 4]]) + * * @category splitting * @since 2.0.0 */ @@ -477,6 +586,12 @@ export const unprepend = ( /** * Return a tuple containing a copy of the `NonEmptyReadonlyArray` without its last element, and that last element. * + * @example + * import { Array } from "effect"; + * + * const result = Array.unappend([1, 2, 3, 4]) + * assert.deepStrictEqual(result, [[1, 2, 3], 4]) + * * @category splitting * @since 2.0.0 */ @@ -493,6 +608,14 @@ export const unappend = ( export const head: (self: ReadonlyArray) => Option = get(0) /** + * Get the first element of a non empty array. + * + * @example + * import { Array } from "effect" + * + * const result = Array.headNonEmpty([1, 2, 3, 4]) + * assert.deepStrictEqual(result, 1) + * * @category getters * @since 2.0.0 */ @@ -508,6 +631,14 @@ export const last = (self: ReadonlyArray): Option => isNonEmptyReadonlyArray(self) ? O.some(lastNonEmpty(self)) : O.none() /** + * Get the last element of a non empty array. + * + * @example + * import { Array } from "effect" + * + * const result = Array.lastNonEmpty([1, 2, 3, 4]) + * assert.deepStrictEqual(result, 4) + * * @category getters * @since 2.0.0 */ @@ -525,6 +656,14 @@ export const tail = (self: Iterable): Option> => { } /** + * Get all but the first element of a `NonEmptyReadonlyArray`. + * + * @example + * import { Array } from "effect" + * + * const result = Array.tailNonEmpty([1, 2, 3, 4]) + * assert.deepStrictEqual(result, [2, 3, 4]) + * * @category getters * @since 2.0.0 */ @@ -544,6 +683,12 @@ export const init = (self: Iterable): Option> => { /** * Get all but the last element of a non empty array, creating a new array. * + * @example + * import { Array } from "effect" + * + * const result = Array.initNonEmpty([1, 2, 3, 4]) + * assert.deepStrictEqual(result, [1, 2, 3]) + * * @category getters * @since 2.0.0 */ @@ -554,6 +699,13 @@ export const initNonEmpty = (self: NonEmptyReadonlyArray): Array => sel * * **Note**. `n` is normalized to a non negative integer. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.take(numbers, 3) + * assert.deepStrictEqual(result, [1, 2, 3]) + * * @category getters * @since 2.0.0 */ @@ -570,6 +722,13 @@ export const take: { * * **Note**. `n` is normalized to a non negative integer. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.takeRight(numbers, 3) + * assert.deepStrictEqual(result, [3, 4, 5]) + * * @category getters * @since 2.0.0 */ @@ -585,6 +744,19 @@ export const takeRight: { /** * Calculate the longest initial subarray for which all element satisfy the specified predicate, creating a new `Array`. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 3, 2, 4, 1, 2] + * const result = Array.takeWhile(numbers, x => x < 4) + * assert.deepStrictEqual(result, [1, 3, 2]) + * + * // Explanation: + * // - The function starts with the first element (`1`), which is less than `4`, so it adds `1` to the result. + * // - The next element (`3`) is also less than `4`, so it adds `3`. + * // - The next element (`2`) is again less than `4`, so it adds `2`. + * // - The function then encounters `4`, which is not less than `4`. At this point, it stops checking further elements and finalizes the result. + * * @category getters * @since 2.0.0 */ @@ -647,6 +819,13 @@ export const span: { * * **Note**. `n` is normalized to a non negative integer. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.drop(numbers, 2) + * assert.deepStrictEqual(result, [3, 4, 5]) + * * @category getters * @since 2.0.0 */ @@ -663,6 +842,13 @@ export const drop: { * * **Note**. `n` is normalized to a non negative integer. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.dropRight(numbers, 2) + * assert.deepStrictEqual(result, [1, 2, 3]) + * * @category getters * @since 2.0.0 */ @@ -677,6 +863,13 @@ export const dropRight: { /** * Remove the longest initial subarray for which all element satisfy the specified predicate, creating a new `Array`. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.dropWhile(numbers, x => x < 4) + * assert.deepStrictEqual(result, [4, 5]) + * * @category getters * @since 2.0.0 */ @@ -692,6 +885,13 @@ export const dropWhile: { /** * Return the first index for which a predicate holds. * + * @example + * import { Array, Option } from "effect" + * + * const numbers = [5, 3, 8, 9] + * const result = Array.findFirstIndex(numbers, x => x > 5) + * assert.deepStrictEqual(result, Option.some(2)) + * * @category elements * @since 2.0.0 */ @@ -712,6 +912,13 @@ export const findFirstIndex: { /** * Return the last index for which a predicate holds. * + * @example + * import { Array, Option } from "effect" + * + * const numbers = [1, 3, 8, 9] + * const result = Array.findLastIndex(numbers, x => x < 5) + * assert.deepStrictEqual(result, Option.some(1)) + * * @category elements * @since 2.0.0 */ @@ -732,6 +939,13 @@ export const findLastIndex: { * Returns the first element that satisfies the specified * predicate, or `None` if no such element exists. * + * @example + * import { Array, Option } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.findFirst(numbers, x => x > 3) + * assert.deepStrictEqual(result, Option.some(4)) + * * @category elements * @since 2.0.0 */ @@ -745,7 +959,15 @@ export const findFirst: { } = EffectIterable.findFirst /** - * Find the last element for which a predicate holds. + * Finds the last element in an iterable collection that satisfies the given predicate or refinement. + * Returns an `Option` containing the found element, or `Option.none` if no element matches. + * + * @example + * import { Array, Option } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.findLast(numbers, n => n % 2 === 0) + * assert.deepStrictEqual(result, Option.some(4)) * * @category elements * @since 2.0.0 @@ -782,6 +1004,13 @@ export const findLast: { * Insert an element at the specified index, creating a new `NonEmptyArray`, * or return `None` if the index is out of bounds. * + * @example + * import { Array, Option } from "effect" + * + * const letters = ['a', 'b', 'c', 'e'] + * const result = Array.insertAt(letters, 3, 'd') + * assert.deepStrictEqual(result, Option.some(['a', 'b', 'c', 'd', 'e'])) + * * @since 2.0.0 */ export const insertAt: { @@ -801,6 +1030,13 @@ export const insertAt: { * Change the element at the specified index, creating a new `Array`, * or return a copy of the input if the index is out of bounds. * + * @example + * import { Array } from "effect" + * + * const letters = ['a', 'b', 'c', 'd'] + * const result = Array.replace(1, 'z')(letters) + * assert.deepStrictEqual(result, ['a', 'z', 'c', 'd']) + * * @since 2.0.0 */ export const replace: { @@ -809,6 +1045,15 @@ export const replace: { } = dual(3, (self: Iterable, i: number, b: B): Array => modify(self, i, () => b)) /** + * Replaces an element in an array with the given value, returning an option of the updated array. + * + * @example + * import { Array, Option } from "effect" + * + * const numbers = [1, 2, 3] + * const result = Array.replaceOption(numbers, 1, 4) + * assert.deepStrictEqual(result, Option.some([1, 4, 3])) + * * @since 2.0.0 */ export const replaceOption: { @@ -823,6 +1068,13 @@ export const replaceOption: { * Apply a function to the element at the specified index, creating a new `Array`, * or return a copy of the input if the index is out of bounds. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4] + * const result = Array.modify(numbers, 2, (n) => n * 2) + * assert.deepStrictEqual(result, [1, 2, 6, 4]) + * * @since 2.0.0 */ export const modify: { @@ -838,6 +1090,16 @@ export const modify: { * Apply a function to the element at the specified index, creating a new `Array`, * or return `None` if the index is out of bounds. * + * @example + * import { Array, Option } from "effect" + * + * const numbers = [1, 2, 3, 4] + * const result = Array.modifyOption(numbers, 2, (n) => n * 2) + * assert.deepStrictEqual(result, Option.some([1, 2, 6, 4])) + * + * const outOfBoundsResult = Array.modifyOption(numbers, 5, (n) => n * 2) + * assert.deepStrictEqual(outOfBoundsResult, Option.none()) + * * @since 2.0.0 */ export const modifyOption: { @@ -858,6 +1120,16 @@ export const modifyOption: { * Delete the element at the specified index, creating a new `Array`, * or return a copy of the input if the index is out of bounds. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4] + * const result = Array.remove(numbers, 2) + * assert.deepStrictEqual(result, [1, 2, 4]) + * + * const outOfBoundsResult = Array.remove(numbers, 5) + * assert.deepStrictEqual(outOfBoundsResult, [1, 2, 3, 4]) + * * @since 2.0.0 */ export const remove: { @@ -875,6 +1147,13 @@ export const remove: { /** * Reverse an `Iterable`, creating a new `Array`. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4] + * const result = Array.reverse(numbers) + * assert.deepStrictEqual(result, [4, 3, 2, 1]) + * * @category elements * @since 2.0.0 */ @@ -903,6 +1182,22 @@ export const sort: { }) /** + * Sorts an array based on a provided mapping function and order. The mapping + * function transforms the elements into a value that can be compared, and the + * order defines how those values should be sorted. + * + * @example + * import { Array, Order } from "effect" + * + * const strings = ["aaa", "b", "cc"] + * const result = Array.sortWith(strings, (s) => s.length, Order.number) + * assert.deepStrictEqual(result, ["b", "cc", "aaa"]) + * + * // Explanation: + * // The array of strings is sorted based on their lengths. The mapping function `(s) => s.length` + * // converts each string into its length, and the `Order.number` specifies that the lengths should + * // be sorted in ascending order. + * * @since 2.0.0 * @category elements */ @@ -919,8 +1214,33 @@ export const sortWith: { ) /** - * Sort the elements of an `Iterable` in increasing order, where elements are compared - * using first `orders[0]`, then `orders[1]`, etc... + * Sorts the elements of an `Iterable` in increasing order based on the provided + * orders. The elements are compared using the first order in `orders`, then the + * second order if the first comparison is equal, and so on. + * + * @example + * import { Array, Order } from "effect" + * + * const users = [ + * { name: "Alice", age: 30 }, + * { name: "Bob", age: 25 }, + * { name: "Charlie", age: 30 } + * ] + * + * const result = Array.sortBy( + * Order.mapInput(Order.number, (user: (typeof users)[number]) => user.age), + * Order.mapInput(Order.string, (user: (typeof users)[number]) => user.name) + * )(users) + * + * assert.deepStrictEqual(result, [ + * { name: "Bob", age: 25 }, + * { name: "Alice", age: 30 }, + * { name: "Charlie", age: 30 } + * ]) + * + * // Explanation: + * // The array of users is sorted first by age in ascending order. When ages are equal, + * // the users are further sorted by name in ascending order. * * @category sorting * @since 2.0.0 @@ -945,6 +1265,14 @@ export const sortBy = | NonEmptyReadonlyArray>( * If one input `Iterable` is short, excess elements of the * longer `Iterable` are discarded. * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2, 3] + * const array2 = ['a', 'b'] + * const result = Array.zip(array1, array2) + * assert.deepStrictEqual(result, [[1, 'a'], [2, 'b']]) + * * @category zipping * @since 2.0.0 */ @@ -962,6 +1290,14 @@ export const zip: { * Apply a function to pairs of elements at the same index in two `Iterable`s, collecting the results in a new `Array`. If one * input `Iterable` is short, excess elements of the longer `Iterable` are discarded. * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2, 3] + * const array2 = [4, 5, 6] + * const result = Array.zipWith(array1, array2, (a, b) => a + b) + * assert.deepStrictEqual(result, [5, 7, 9]) + * * @category zipping * @since 2.0.0 */ @@ -987,6 +1323,12 @@ export const zipWith: { /** * This function is the inverse of `zip`. Takes an `Iterable` of pairs and return two corresponding `Array`s. * + * @example + * import { Array } from "effect" + * + * const result = Array.unzip([[1, "a"], [2, "b"], [3, "c"]]) + * assert.deepStrictEqual(result, [[1, 2, 3], ['a', 'b', 'c']]) + * * @since 2.0.0 */ export const unzip: | NonEmptyReadonlyArray>( @@ -1011,6 +1353,13 @@ export const unzip: | NonEmptyReadonlyA * Places an element in between members of an `Iterable`. * If the input is a non-empty array, the result is also a non-empty array. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * const result = Array.intersperse(numbers, 0) + * assert.deepStrictEqual(result, [1, 0, 2, 0, 3]) + * * @since 2.0.0 */ export const intersperse: { @@ -1038,6 +1387,12 @@ export const intersperse: { /** * Apply a function to the head, creating a new `NonEmptyReadonlyArray`. * + * @example + * import { Array } from "effect" + * + * const result = Array.modifyNonEmptyHead([1, 2, 3], n => n * 10) + * assert.deepStrictEqual(result, [10, 2, 3]) + * * @since 2.0.0 */ export const modifyNonEmptyHead: { @@ -1054,6 +1409,12 @@ export const modifyNonEmptyHead: { /** * Change the head, creating a new `NonEmptyReadonlyArray`. * + * @example + * import { Array } from "effect" + * + * const result = Array.setNonEmptyHead([1, 2, 3], 10) + * assert.deepStrictEqual(result, [10, 2, 3]) + * * @since 2.0.0 */ export const setNonEmptyHead: { @@ -1067,6 +1428,12 @@ export const setNonEmptyHead: { /** * Apply a function to the last element, creating a new `NonEmptyReadonlyArray`. * + * @example + * import { Array } from "effect" + * + * const result = Array.modifyNonEmptyLast([1, 2, 3], n => n * 2) + * assert.deepStrictEqual(result, [1, 2, 6]) + * * @since 2.0.0 */ export const modifyNonEmptyLast: { @@ -1081,6 +1448,12 @@ export const modifyNonEmptyLast: { /** * Change the last element, creating a new `NonEmptyReadonlyArray`. * + * @example + * import { Array } from "effect" + * + * const result = Array.setNonEmptyLast([1, 2, 3], 4) + * assert.deepStrictEqual(result, [1, 2, 4]) + * * @since 2.0.0 */ export const setNonEmptyLast: { @@ -1095,6 +1468,13 @@ export const setNonEmptyLast: { * Rotate an `Iterable` by `n` steps. * If the input is a non-empty array, the result is also a non-empty array. * + * @example + * import { Array } from "effect" + * + * const letters = ['a', 'b', 'c', 'd'] + * const result = Array.rotate(letters, 2) + * assert.deepStrictEqual(result, ['c', 'd', 'a', 'b']) + * * @since 2.0.0 */ export const rotate: { @@ -1122,6 +1502,15 @@ export const rotate: { /** * Returns a function that checks if a `ReadonlyArray` contains a given value using a provided `isEquivalent` function. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4] + * const isEquivalent = (a: number, b: number) => a === b + * const containsNumber = Array.containsWith(isEquivalent) + * const result = containsNumber(3)(numbers) + * assert.deepStrictEqual(result, true) + * * @category elements * @since 2.0.0 */ @@ -1143,6 +1532,13 @@ const _equivalence = Equal.equivalence() /** * Returns a function that checks if a `ReadonlyArray` contains a given value using the default `Equivalence`. * + * @example + * import { Array } from "effect" + * + * const letters = ['a', 'b', 'c', 'd'] + * const result = Array.contains('c')(letters) + * assert.deepStrictEqual(result, true) + * * @category elements * @since 2.0.0 */ @@ -1156,6 +1552,18 @@ export const contains: { * `Iterable`. Typically chop is called with some function that will consume an initial prefix of the `Iterable` and produce a * value and the rest of the `Array`. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.chop(numbers, (as): [number, Array] => [as[0] * 2, as.slice(1)]) + * assert.deepStrictEqual(result, [2, 4, 6, 8, 10]) + * + * // Explanation: + * // The `chopFunction` takes the first element of the array, doubles it, and then returns it along with the rest of the array. + * // The `chop` function applies this `chopFunction` recursively to the input array `[1, 2, 3, 4, 5]`, + * // resulting in a new array `[2, 4, 6, 8, 10]`. + * * @since 2.0.0 */ export const chop: { @@ -1193,6 +1601,13 @@ export const chop: { * Splits an `Iterable` into two segments, with the first segment containing a maximum of `n` elements. * The value of `n` can be `0`. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.splitAt(numbers, 3) + * assert.deepStrictEqual(result, [[1, 2, 3], [4, 5]]) + * * @category splitting * @since 2.0.0 */ @@ -1215,6 +1630,12 @@ export const splitAt: { * Splits a `NonEmptyReadonlyArray` into two segments, with the first segment containing a maximum of `n` elements. * The value of `n` must be `>= 1`. * + * @example + * import { Array } from "effect" + * + * const result = Array.splitNonEmptyAt(["a", "b", "c", "d", "e"], 3) + * assert.deepStrictEqual(result, [["a", "b", "c"], ["d", "e"]]) + * * @category splitting * @since 2.0.0 */ @@ -1231,6 +1652,13 @@ export const splitNonEmptyAt: { /** * Splits this iterable into `n` equally sized arrays. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5, 6, 7, 8] + * const result = Array.split(numbers, 3) + * assert.deepStrictEqual(result, [[1, 2, 3], [4, 5, 6], [7, 8]]) + * * @since 2.0.0 * @category splitting */ @@ -1246,6 +1674,13 @@ export const split: { * Splits this iterable on the first element that matches this predicate. * Returns a tuple containing two arrays: the first one is before the match, and the second one is from the match onward. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.splitWhere(numbers, n => n > 3) + * assert.deepStrictEqual(result, [[1, 2, 3], [4, 5]]) + * * @category splitting * @since 2.0.0 */ @@ -1261,6 +1696,15 @@ export const splitWhere: { ) /** + * Copies an array. + * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * const copy = Array.copy(numbers) + * assert.deepStrictEqual(copy, [1, 2, 3]) + * * @since 2.0.0 */ export const copy: { @@ -1279,6 +1723,19 @@ export const copy: { * * whenever `n` evenly divides the length of `self`. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const result = Array.chunksOf(numbers, 2) + * assert.deepStrictEqual(result, [[1, 2], [3, 4], [5]]) + * + * // Explanation: + * // The `chunksOf` function takes an array of numbers `[1, 2, 3, 4, 5]` and a number `2`. + * // It splits the array into chunks of length 2. Since the array length is not evenly divisible by 2, + * // the last chunk contains the remaining elements. + * // The result is `[[1, 2], [3, 4], [5]]`. + * * @category splitting * @since 2.0.0 */ @@ -1301,6 +1758,12 @@ export const chunksOf: { /** * Group equal, consecutive elements of a `NonEmptyReadonlyArray` into `NonEmptyArray`s using the provided `isEquivalent` function. * + * @example + * import { Array } from "effect" + * + * const result = Array.groupWith(["a", "a", "b", "b", "b", "c", "a"], (x, y) => x === y) + * assert.deepStrictEqual(result, [["a", "a"], ["b", "b", "b"], ["c"], ["a"]]) + * * @category grouping * @since 2.0.0 */ @@ -1329,6 +1792,12 @@ export const groupWith: { /** * Group equal, consecutive elements of a `NonEmptyReadonlyArray` into `NonEmptyArray`s. * + * @example + * import { Array } from "effect" + * + * const result = Array.group([1, 1, 2, 2, 2, 3, 1]) + * assert.deepStrictEqual(result, [[1, 1], [2, 2, 2], [3], [1]]) + * * @category grouping * @since 2.0.0 */ @@ -1340,6 +1809,20 @@ export const group: (self: NonEmptyReadonlyArray) => NonEmptyArray person.group) + * assert.deepStrictEqual(result, { + * A: [{ name: "Alice", group: "A" }, { name: "Charlie", group: "A" }], + * B: [{ name: "Bob", group: "B" }] + * }) + * * @category grouping * @since 2.0.0 */ @@ -1368,6 +1851,16 @@ export const groupBy: { }) /** + * Calculates the union of two arrays using the provided equivalence relation. + * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2] + * const array2 = [2, 3] + * const union = Array.unionWith(array1, array2, (a, b) => a === b) + * assert.deepStrictEqual(union, [1, 2, 3]) + * * @since 2.0.0 */ export const unionWith: { @@ -1400,6 +1893,16 @@ export const unionWith: { }) /** + * Creates a union of two arrays, removing duplicates. + * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2] + * const array2 = [2, 3] + * const result = Array.union(array1, array2) + * assert.deepStrictEqual(result, [1, 2, 3]) + * * @since 2.0.0 */ export const union: { @@ -1417,6 +1920,15 @@ export const union: { * Creates an `Array` of unique values that are included in all given `Iterable`s using the provided `isEquivalent` function. * The order and references of result values are determined by the first `Iterable`. * + * @example + * import { Array } from "effect" + * + * const array1 = [{ id: 1 }, { id: 2 }, { id: 3 }] + * const array2 = [{ id: 3 }, { id: 4 }, { id: 1 }] + * const isEquivalent = (a: { id: number }, b: { id: number }) => a.id === b.id + * const result = Array.intersectionWith(isEquivalent)(array2)(array1) + * assert.deepStrictEqual(result, [{ id: 1 }, { id: 3 }]) + * * @since 2.0.0 */ export const intersectionWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -1434,6 +1946,14 @@ export const intersectionWith = (isEquivalent: (self: A, that: A) => boolean) * Creates an `Array` of unique values that are included in all given `Iterable`s. * The order and references of result values are determined by the first `Iterable`. * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2, 3] + * const array2 = [3, 4, 1] + * const result = Array.intersection(array1, array2) + * assert.deepStrictEqual(result, [1, 3]) + * * @since 2.0.0 */ export const intersection: { @@ -1445,6 +1965,14 @@ export const intersection: { * Creates a `Array` of values not included in the other given `Iterable` using the provided `isEquivalent` function. * The order and references of result values are determined by the first `Iterable`. * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2, 3] + * const array2 = [2, 3, 4] + * const difference = Array.differenceWith((a, b) => a === b)(array1, array2) + * assert.deepStrictEqual(difference, [1]) + * * @since 2.0.0 */ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -1462,6 +1990,14 @@ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): * Creates a `Array` of values not included in the other given `Iterable`. * The order and references of result values are determined by the first `Iterable`. * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2, 3] + * const array2 = [2, 3, 4] + * const difference = Array.difference(array1, array2) + * assert.deepStrictEqual(difference, [1]) + * * @since 2.0.0 */ export const difference: { @@ -1573,7 +2109,17 @@ export const flatMap: { ) /** - * Flattens an array of arrays into a single array by concatenating all arrays. + * Combines multiple arrays into a single array by concatenating all elements + * from each nested array. This function ensures that the structure of nested + * arrays is collapsed into a single, flat array. + * + * @example + * import { Array } from "effect"; + * + * const nestedArrays = [[1, 2], [], [3, 4], [], [5, 6]] + * const result = Array.flatten(nestedArrays) + * + * assert.deepStrictEqual(result, [1, 2, 3, 4, 5, 6]); * * @category sequencing * @since 2.0.0 @@ -1583,6 +2129,18 @@ export const flatten: >>(self: S) => ) as any /** + * Applies a function to each element of the `Iterable` and filters based on the result, keeping the transformed values where the function returns `Some`. + * This method combines filtering and mapping functionalities, allowing transformations and filtering of elements based on a single function pass. + * + * @example + * import { Array, Option } from "effect"; + * + * const data = [1, 2, 3, 4, 5]; + * const evenSquares = (x: number) => x % 2 === 0 ? Option.some(x * x) : Option.none(); + * const result = Array.filterMap(data, evenSquares); + * + * assert.deepStrictEqual(result, [4, 16]); + * * @category filtering * @since 2.0.0 */ @@ -1605,7 +2163,18 @@ export const filterMap: { ) /** - * Transforms all elements of the `readonlyArray` for as long as the specified function returns some value + * Applies a function to each element of the array and filters based on the result, stopping when a condition is not met. + * This method combines filtering and mapping in a single pass, and short-circuits, i.e., stops processing, as soon as the function returns `None`. + * This is useful when you need to transform an array but only up to the point where a certain condition holds true. + * + * @example + * import { Array, Option } from "effect"; + * + * const data = [2, 4, 5]; + * const toSquareTillOdd = (x: number) => x % 2 === 0 ? Option.some(x * x) : Option.none(); + * const result = Array.filterMapWhile(data, toSquareTillOdd); + * + * assert.deepStrictEqual(result, [4, 16]); * * @category filtering * @since 2.0.0 @@ -1629,6 +2198,25 @@ export const filterMapWhile: { }) /** + * Applies a function to each element of the `Iterable`, categorizing the results into two separate arrays. + * This function is particularly useful for operations where each element can result in two possible types, + * and you want to separate these types into different collections. For instance, separating validation results + * into successes and failures. + * + * @example + * import { Array, Either } from "effect"; + * + * const data = [1, 2, 3, 4, 5] + * const isEven = (x: number) => x % 2 === 0 + * const partitioned = Array.partitionMap(data, x => + * isEven(x) ? Either.right(x) : Either.left(x) + * ) + * + * assert.deepStrictEqual(partitioned, [ + * [1, 3, 5], + * [2, 4] + * ]) + * * @category filtering * @since 2.0.0 */ @@ -1778,6 +2366,15 @@ export const partition: { ) /** + * Separates an `Iterable` into two arrays based on a predicate. + * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3, 4] + * const result = Array.partition(numbers, n => n % 2 === 0) + * assert.deepStrictEqual(result, [[1, 3], [2, 4]]) + * * @category filtering * @since 2.0.0 */ @@ -1786,6 +2383,15 @@ export const separate: (self: Iterable>) => [Array, Array< ) /** + * Reduces an array from the left. + * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * const result = Array.reduce(numbers, 0, (acc, n) => acc + n) + * assert.deepStrictEqual(result, 6) + * * @category folding * @since 2.0.0 */ @@ -1799,6 +2405,15 @@ export const reduce: { ) /** + * Reduces an array from the right. + * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * const result = Array.reduceRight(numbers, 0, (acc, n) => acc + n) + * assert.deepStrictEqual(result, 6) + * * @category folding * @since 2.0.0 */ @@ -1812,6 +2427,16 @@ export const reduceRight: { ) /** + * Lifts a predicate into an array. + * + * @example + * import { Array } from "effect" + * + * const isEven = (n: number) => n % 2 === 0 + * const to = Array.liftPredicate(isEven) + * assert.deepStrictEqual(to(1), []) + * assert.deepStrictEqual(to(2), [2]) + * * @category lifting * @since 2.0.0 */ @@ -1845,6 +2470,20 @@ export const liftNullable = , B>( (...a) => fromNullable(f(...a)) /** + * Maps over an array and flattens the result, removing null and undefined values. + * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * const result = Array.flatMapNullable(numbers, n => (n % 2 === 0 ? null : n)) + * assert.deepStrictEqual(result, [1, 3]) + * + * // Explanation: + * // The array of numbers [1, 2, 3] is mapped with a function that returns null for even numbers + * // and the number itself for odd numbers. The resulting array [1, null, 3] is then flattened + * // to remove null values, resulting in [1, 3]. + * * @category sequencing * @since 2.0.0 */ @@ -1854,10 +2493,33 @@ export const flatMapNullable: { } = dual( 2, (self: ReadonlyArray, f: (a: A) => B | null | undefined): Array> => - isNonEmptyReadonlyArray(self) ? fromNullable(f(headNonEmpty(self))) : empty() + flatMap(self, (a) => fromNullable(f(a))) ) /** + * Lifts a function that returns an `Either` into a function that returns an array. + * If the `Either` is a left, it returns an empty array. + * If the `Either` is a right, it returns an array with the right value. + * + * @example + * import { Array, Either } from "effect" + * + * const parseNumber = (s: string): Either.Either => + * isNaN(Number(s)) ? Either.left(new Error("Not a number")) : Either.right(Number(s)) + * + * const liftedParseNumber = Array.liftEither(parseNumber) + * + * const result1 = liftedParseNumber("42") + * assert.deepStrictEqual(result1, [42]) + * + * const result2 = liftedParseNumber("not a number") + * assert.deepStrictEqual(result2, []) + * + * // Explanation: + * // The function parseNumber is lifted to return an array. + * // When parsing "42", it returns an Either.left with the number 42, resulting in [42]. + * // When parsing "not a number", it returns an Either.right with an error, resulting in an empty array []. + * * @category lifting * @since 2.0.0 */ @@ -1906,6 +2568,21 @@ export const some: { ) /** + * Extends an array with a function that maps each subarray to a value. + * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * const result = Array.extend(numbers, as => as.length) + * assert.deepStrictEqual(result, [3, 2, 1]) + * + * // Explanation: + * // The function maps each subarray starting from each element to its length. + * // The subarrays are: [1, 2, 3], [2, 3], [3]. + * // The lengths are: 3, 2, 1. + * // Therefore, the result is [3, 2, 1]. + * * @since 2.0.0 */ export const extend: { @@ -1917,6 +2594,14 @@ export const extend: { ) /** + * Finds the minimum element in an array based on a comparator. + * + * @example + * import { Array, Order } from "effect" + * + * const min = Array.min([3, 1, 2], Order.number) + * assert.deepStrictEqual(min, 1) + * * @since 2.0.0 */ export const min: { @@ -1925,6 +2610,14 @@ export const min: { } = dual(2, (self: NonEmptyReadonlyArray, O: Order.Order): A => self.reduce(Order.min(O))) /** + * Finds the maximum element in an array based on a comparator. + * + * @example + * import { Array, Order } from "effect" + * + * const max = Array.max([3, 1, 2], Order.number) + * assert.deepStrictEqual(max, 3) + * * @since 2.0.0 */ export const max: { @@ -1960,6 +2653,16 @@ export const unfold = (b: B, f: (b: B) => Option): Array< export const getOrder: (O: Order.Order) => Order.Order> = Order.array /** + * Creates an equivalence relation for arrays. + * + * @example + * import { Array } from "effect" + * + * const numbers1 = [1, 2, 3] + * const numbers2 = [1, 2, 3] + * const eq = Array.getEquivalence((a, b) => a === b) + * assert.deepStrictEqual(eq(numbers1, numbers2), true) + * * @category instances * @since 2.0.0 */ @@ -1968,7 +2671,13 @@ export const getEquivalence: ( ) => Equivalence.Equivalence> = Equivalence.array /** - * Iterate over the `Iterable` applying `f`. + * Performs a side-effect for each element of the `Iterable`. + * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * Array.forEach(numbers, n => console.log(n)) // 1, 2, 3 * * @since 2.0.0 */ @@ -1981,6 +2690,13 @@ export const forEach: { * Remove duplicates from an `Iterable` using the provided `isEquivalent` function, * preserving the order of the first occurrence of each element. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 2, 3, 3, 3] + * const unique = Array.dedupeWith(numbers, (a, b) => a === b) + * assert.deepStrictEqual(unique, [1, 2, 3]) + * * @since 2.0.0 */ export const dedupeWith: { @@ -2021,6 +2737,13 @@ export const dedupe = | NonEmptyReadonlyArray>( /** * Deduplicates adjacent elements that are identical using the provided `isEquivalent` function. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 1, 2, 2, 3, 3] + * const unique = Array.dedupeAdjacentWith(numbers, (a, b) => a === b) + * assert.deepStrictEqual(unique, [1, 2, 3]) + * * @since 2.0.0 */ export const dedupeAdjacentWith: { @@ -2041,6 +2764,13 @@ export const dedupeAdjacentWith: { /** * Deduplicates adjacent elements that are identical. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 1, 2, 2, 3, 3] + * const unique = Array.dedupeAdjacent(numbers) + * assert.deepStrictEqual(unique, [1, 2, 3]) + * * @since 2.0.0 */ export const dedupeAdjacent: (self: Iterable) => Array = dedupeAdjacentWith(Equal.equivalence()) @@ -2048,6 +2778,13 @@ export const dedupeAdjacent: (self: Iterable) => Array = dedupeAdjacent /** * Joins the elements together with "sep" in the middle. * + * @example + * import { Array } from "effect" + * + * const strings = ["a", "b", "c"] + * const joined = Array.join(strings, "-") + * assert.deepStrictEqual(joined, "a-b-c") + * * @since 2.0.0 * @category folding */ @@ -2059,6 +2796,13 @@ export const join: { /** * Statefully maps over the chunk, producing new elements of type `B`. * + * @example + * import { Array } from "effect" + * + * const numbers = [1, 2, 3] + * const result = Array.mapAccum(numbers, 0, (acc, n) => [acc + n, acc + n]) + * assert.deepStrictEqual(result, [6, [1, 3, 6]]) + * * @since 2.0.0 * @category folding */ @@ -2087,6 +2831,14 @@ export const mapAccum: { /** * Zips this chunk crosswise with the specified chunk using the specified combiner. * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2] + * const array2 = ["a", "b"] + * const product = Array.cartesianWith(array1, array2, (a, b) => `${a}-${b}`) + * assert.deepStrictEqual(product, ["1-a", "1-b", "2-a", "2-b"]) + * * @since 2.0.0 * @category elements */ @@ -2102,6 +2854,14 @@ export const cartesianWith: { /** * Zips this chunk crosswise with the specified chunk. * + * @example + * import { Array } from "effect" + * + * const array1 = [1, 2] + * const array2 = ["a", "b"] + * const product = Array.cartesian(array1, array2) + * assert.deepStrictEqual(product, [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]) + * * @since 2.0.0 * @category elements */ diff --git a/packages/effect/test/Array.test.ts b/packages/effect/test/Array.test.ts index bb08ca7b3a..6d73e9bdf1 100644 --- a/packages/effect/test/Array.test.ts +++ b/packages/effect/test/Array.test.ts @@ -652,7 +652,9 @@ describe("ReadonlyArray", () => { const f = RA.flatMapNullable((n: number) => (n > 0 ? n : null)) deepStrictEqual(pipe([], f), []) deepStrictEqual(pipe([1], f), [1]) + deepStrictEqual(pipe([1, 2], f), [1, 2]) deepStrictEqual(pipe([-1], f), []) + deepStrictEqual(pipe([-1, 2], f), [2]) }) it("liftPredicate", () => {