diff --git a/.changeset/chatty-flowers-applaud.md b/.changeset/chatty-flowers-applaud.md new file mode 100644 index 0000000..01c468f --- /dev/null +++ b/.changeset/chatty-flowers-applaud.md @@ -0,0 +1,5 @@ +--- +'funkcia': patch +--- + +Add JSDoc data and examples for Array and Predicate methods diff --git a/src/array.spec.ts b/src/array.spec.ts index 3a1aa35..7e47258 100644 --- a/src/array.spec.ts +++ b/src/array.spec.ts @@ -70,41 +70,6 @@ describe('Array', () => { }); describe('getters', () => { - describe('take', () => { - describe('data-first', () => { - it('returns a new array with only the amount to be taken', () => { - expect(A.take([1, 2, 3], 2)).toEqual([1, 2]); - }); - }); - - describe('data-last', () => { - it('returns a new array with only the amount to be taken', () => { - expect(pipe([1, 2, 3], A.take(2))).toEqual([1, 2]); - }); - }); - }); - - describe('takeWhile', () => { - describe('data-first', () => { - it('returns a new array with all elements until the first index that does not satisfy the predicate', () => { - expect(A.takeWhile([1, 2, 3, 2, 1], (number) => number < 3)).toEqual([ - 1, 2, - ]); - }); - }); - - describe('data-last', () => { - it('returns a new array with all elements until the first index that does not satisfy the predicate', () => { - expect( - pipe( - [1, 2, 3, 2, 1], - A.takeWhile((number) => number < 3), - ), - ).toEqual([1, 2]); - }); - }); - }); - describe('head', () => { it('returns a Some with the first element if the array is not empty', () => { expect(A.head([1, 2, 3, 4, 5])).toMatchOption(O.some(1)); @@ -226,6 +191,41 @@ describe('Array', () => { }); }); + describe('take', () => { + describe('data-first', () => { + it('returns a new array with only the amount to be taken', () => { + expect(A.take([1, 2, 3], 2)).toEqual([1, 2]); + }); + }); + + describe('data-last', () => { + it('returns a new array with only the amount to be taken', () => { + expect(pipe([1, 2, 3], A.take(2))).toEqual([1, 2]); + }); + }); + }); + + describe('takeWhile', () => { + describe('data-first', () => { + it('returns a new array with all elements until the first index that does not satisfy the predicate', () => { + expect(A.takeWhile([1, 2, 3, 2, 1], (number) => number < 3)).toEqual([ + 1, 2, + ]); + }); + }); + + describe('data-last', () => { + it('returns a new array with all elements until the first index that does not satisfy the predicate', () => { + expect( + pipe( + [1, 2, 3, 2, 1], + A.takeWhile((number) => number < 3), + ), + ).toEqual([1, 2]); + }); + }); + }); + describe('drop', () => { describe('data-first', () => { it('returns a new array dropping the amount of items informed', () => { @@ -449,9 +449,9 @@ describe('Array', () => { expect(A.difference([], [])).toEqual([]); expect(A.difference([1, 2, 3, 4], [])).toEqual([1, 2, 3, 4]); - expect(A.difference([], [3, 4, 5])).toEqual([3, 4, 5]); + expect(A.difference([], [3, 4, 5])).toEqual([]); - expect(A.difference([1, 2, 3, 4], [3, 4, 5])).toEqual([1, 2, 5]); + expect(A.difference([1, 2, 3, 4], [3, 4, 5])).toEqual([1, 2]); }); }); @@ -462,11 +462,9 @@ describe('Array', () => { expect(pipe([1, 2, 3, 4], A.difference([]))).toEqual([ 1, 2, 3, 4, ]); - expect(pipe([], A.difference([3, 4, 5]))).toEqual([3, 4, 5]); + expect(pipe([], A.difference([3, 4, 5]))).toEqual([]); - expect(pipe([1, 2, 3, 4], A.difference([3, 4, 5]))).toEqual([ - 1, 2, 5, - ]); + expect(pipe([1, 2, 3, 4], A.difference([3, 4, 5]))).toEqual([1, 2]); }); }); }); diff --git a/src/array.ts b/src/array.ts index 1ffebd1..333f32a 100644 --- a/src/array.ts +++ b/src/array.ts @@ -1,14 +1,16 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { dual } from './_internals/dual'; -import { isNone } from './_internals/option'; +import { + isNone, + none as noneOption, + some as someOption, +} from './_internals/option'; import { identity } from './functions'; import type { Option } from './option'; import { - none as noneOption, fromNullable as optionFromNullable, fromPredicate as optionFromPredicate, - some as someOption, } from './option'; import { not } from './predicate'; @@ -16,7 +18,28 @@ import { not } from './predicate'; // constructors // ------------------------------------- +/** + * Creates a new array with the provided size, filling each position with its index value + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const list = A.create(3); // [0, 1, 2] + * ``` + */ export function create(size: number): number[]; +/** + * Creates a new array with the provided size, filling each position with the result of the callback to create each element + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const numbers = A.create(3, index => index + 1); // [1, 2, 3] + * const names = A.create(2, generateRandomName); // ['John Doe', 'Fred Nerk'] + * ``` + */ export function create( size: number, createElement: (index: number) => A, @@ -28,10 +51,31 @@ export function create( return Array.from({ length: size }, (_, index) => createElement(index)); } +/** + * Returns a new empty array of the provided type + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const list = A.empty(); + * //^? number[] + * ``` + */ export function empty(): A[] { return []; } +/** + * Returns an array filled with values in the provided range + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const positives = A.range(1, 5); // [1, 2, 3, 4, 5] + * ``` + */ export function range(start: number, finish: number): number[] { const offset = 1; @@ -46,16 +90,61 @@ export function range(start: number, finish: number): number[] { // refinements // ------------------------------------- -export function isArray(value: unknown): value is unknown[] { - return Array.isArray(value); +/** + * Type guard that asserts a variable is an array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * declare const data: unknown; + * + * if (A.isArray(data)) { + * const result = data.map(transformValue); + * //^? unknown[] + * } + * ``` + */ +export function isArray(arg: unknown): arg is unknown[] { + return Array.isArray(arg); } +/** + * Type guard that asserts an array is an empty array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * declare const data: number[]; + * + * if (A.isEmpty(data)) { + * return data; + * //^? [] + * } + * ``` + */ export function isEmpty(self: readonly A[]): self is [] { return self.length === 0; } export type NonEmptyArray = [A, ...A[]]; +/** + * Type guard that asserts an array is a non-empty array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const data: number[] = [1, 2, 3]; + * + * if (A.isNonEmpty(data)) { + * const first = data[0]; + * //^? [number, ...number[]] + * } + * ``` + */ export function isNonEmpty(self: readonly A[]): self is NonEmptyArray { return self.length > 0; } @@ -64,57 +153,78 @@ export function isNonEmpty(self: readonly A[]): self is NonEmptyArray { // getters // ------------------------------------- -interface Take { - (amount: number): (self: readonly A[]) => A[]; - (self: readonly A[], amount: number): A[]; -} - -export const take: Take = dual(2, (self: readonly any[], amount: number) => { - if (amount <= 0) { - return []; - } - - return self.slice(0, amount); -}); - -interface TakeWhile { - (predicate: (item: A) => boolean): (self: readonly A[]) => A[]; - (self: readonly A[], predicate: (item: A) => boolean): A[]; -} - -export const takeWhile: TakeWhile = dual( - 2, - (self: any[], predicate: (item: any) => boolean): any[] => { - const indexOfFirstElementThatDoesNotSatisfiesPredicate = self.findIndex( - not(predicate), - ); - - if (indexOfFirstElementThatDoesNotSatisfiesPredicate < 0) { - return []; - } - - return self.slice(0, indexOfFirstElementThatDoesNotSatisfiesPredicate); - }, -); - +/** + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.head([1, 2, 3]); + * //^? Some<1> + * ``` + */ export function head(self: readonly A[]): Option { return isNonEmpty(self) ? someOption(self[0]) : noneOption(); } +/** + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.last([1, 2, 3]); + * //^? Some<3> + * ``` + */ export function last(self: readonly A[]): Option { return isNonEmpty(self) ? someOption(self[self.length - 1]!) : noneOption(); } +/** + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.init([1, 2, 3]); + * //^? Some<[1, 2]> + * ``` + */ export function init(self: readonly A[]): Option> { + const offset = 1; + return isNonEmpty(self) - ? someOption(self.slice(0, self.length - 1) as any) + ? someOption(self.slice(0, self.length - offset) as any) : noneOption(); } +/** + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.tail([1, 2, 3]); + * //^? Some<[2, 3]> + * ``` + */ export function tail(self: readonly A[]): Option> { return isNonEmpty(self) ? someOption(self.slice(1) as any) : noneOption(); } +/** + * Takes an integer value and returns an Option with the item at that index, allowing for positive and negative integers. + * Negative integers count back from the last item in the array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.at(1)); + * //^? Some<2> + * ``` + */ export function at(index: number): (self: readonly A[]) => Option { return (self) => { const size = self.length; @@ -139,9 +249,33 @@ export function at(index: number): (self: readonly A[]) => Option { }; } +/** + * Returns a Some with the value of the first element in the array where predicate is true, and None otherwise + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * type ValidNumber = number; + * + * const result = pipe([1, 2, 3], A.find((number): number is ValidNumber => number > 2)); + * //^? Some + * ``` + */ export function find( refinement: (item: A, index: number, array: readonly A[]) => item is A2, ): (self: readonly A[]) => Option; +/** + * Returns a Some with the value of the first element in the array where predicate is true, and None otherwise + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.find(number => number > 2)); + * //^? Some<3> + * ``` + */ export function find( predicate: (item: A, index: number, array: readonly A[]) => boolean, ): (self: readonly A[]) => Option; @@ -151,6 +285,17 @@ export function find( return (self) => optionFromNullable(self.find(predicate)); } +/** + * Returns a Some with the index of the first element in the array where predicate is true, and None otherwise + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.findIndex(number => number >= 3)); + * //^? Some<2> + * ``` + */ export function findIndex( predicate: (item: A, index: number, array: readonly A[]) => boolean, ): (self: readonly A[]) => Option { @@ -158,6 +303,16 @@ export function findIndex( optionFromPredicate(self.findIndex(predicate), (index) => index >= 0); } +/** + * Gets the length of the array. This is a number one higher than the highest element defined in an array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const size = pipe([1, 2, 3], A.length); // 3 + * ``` + */ export function length(self: readonly A[]): number { return self.length; } @@ -166,69 +321,312 @@ export function length(self: readonly A[]): number { // transforming // ------------------------------------- +/** + * Calls a defined callback function on each element of an array, and returns an array that contains the results + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([0, 1, 2], A.map(number => number + 1)); // [1, 2, 3] + * ``` + */ export function map( callback: (item: A, index: number, array: readonly A[]) => B, ): (self: readonly A[]) => B[] { return (self) => self.map(callback); } +/** + * Calls a defined callback function on each element of an array. Then, flattens the result into a new array. + * This is identical to a map followed by flat with depth 1 + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([0, 1, 2], A.flatMap(number => [number + 1])); // [1, 2, 3] + * ``` + */ export function flatMap( callback: (item: A, index: number, array: readonly A[]) => B | readonly B[], ): (self: readonly A[]) => B[] { return (self) => self.flatMap(callback); } +interface Take { + /** + * Takes the specified number of items from the given array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.take(2)); // [1, 2] + * ``` + */ + (amount: number): (self: readonly A[]) => A[]; + /** + * Takes the specified number of items from the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.take([1, 2, 3], 2); // [1, 2] + * ``` + */ + (self: readonly A[], amount: number): A[]; +} + +/** + * Takes the specified number of items from the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.take([1, 2, 3], 2); // [1, 2] + * ``` + */ +export const take: Take = dual(2, (self: readonly any[], amount: number) => { + if (amount <= 0) { + return []; + } + + return self.slice(0, amount); +}); + +interface TakeWhile { + /** + * Takes items from the given array while the predicate is satisfied + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.takeWhile(number => number < 3)); // [1, 2] + * ``` + */ + (predicate: (item: A) => boolean): (self: readonly A[]) => A[]; + /** + * Takes items from the given array while the predicate is satisfied + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.takeWhile([1, 2, 3], number => number < 3); // [1, 2] + * ``` + */ + (self: readonly A[], predicate: (item: A) => boolean): A[]; +} + +/** + * Takes items from the given array while the predicate is satisfied + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.takeWhile([1, 2, 3], number => number < 3); // [1, 2] + * ``` + */ +export const takeWhile: TakeWhile = dual( + 2, + (self: any[], predicate: (item: any) => boolean): any[] => { + const indexOfFirstElementThatDoesNotSatisfyPredicate = self.findIndex( + not(predicate), + ); + + if (indexOfFirstElementThatDoesNotSatisfyPredicate < 0) { + return []; + } + + return self.slice(0, indexOfFirstElementThatDoesNotSatisfyPredicate); + }, +); + interface Drop { + /** + * Drops the specified number of items from the given array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3, 4, 5], A.drop(2)); // [3, 4, 5] + * ``` + */ (howMany: number): (self: readonly A[]) => A[]; + /** + * Drops the specified number of items from the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.drop([1, 2, 3, 4, 5], 2); // [3, 4, 5] + * ``` + */ (self: readonly A[], howMany: number): A[]; } +/** + * Drops the specified number of items from the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.drop([1, 2, 3, 4, 5], 2); // [3, 4, 5] + * ``` + */ export const drop: Drop = dual(2, (self: readonly any[], howMany: number) => self.slice(howMany), ); interface DropWhile { + /** + * Drops items from the given array while the predicate is satisfied + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3, 4, 5], A.drop(number => number < 3)); // [3, 4, 5] + * ``` + */ (predicate: (item: A) => boolean): (self: readonly A[]) => A[]; + /** + * Drops items from the given array while the predicate is satisfied + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.drop([1, 2, 3, 4, 5], number => number < 3); // [3, 4, 5] + * ``` + */ (self: readonly A[], predicate: (item: A) => boolean): A[]; } +/** + * Drops items from the given array while the predicate is satisfied + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.drop([1, 2, 3, 4, 5], number => number < 3); // [3, 4, 5] + * ``` + */ export const dropWhile: DropWhile = dual( 2, (self: any[], predicate: (item: any) => boolean): any[] => { - const copy = [...self]; - - for (const item of self) { - if (!predicate(item)) { - break; - } + const indexOfFirstElementThatDoesNotSatisfyPredicate = self.findIndex( + not(predicate), + ); - copy.shift(); + if (indexOfFirstElementThatDoesNotSatisfyPredicate < 0) { + return [...self]; } - return copy; + return self.slice(indexOfFirstElementThatDoesNotSatisfyPredicate); }, ); interface Append { + /** + * Adds an item to the end of the given array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3, 4], A.append(5)); // [1, 2, 3, 4, 5] + * ``` + */ (item: A): (self: readonly A[]) => A[]; + /** + * Adds an item to the end of the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.append([1, 2, 3, 4], 5); // [1, 2, 3, 4, 5] + * ``` + */ (self: readonly A[], item: A): A[]; } +/** + * Adds an item to the end of the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.append([1, 2, 3, 4], 5); // [1, 2, 3, 4, 5] + * ``` + */ export const append: Append = dual(2, (self: readonly any[], item: any) => [ ...self, item, ]); interface Prepend { + /** + * Adds an item to the start of the given array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3, 4], A.prepend(0)); // [0, 1, 2, 3, 4] + * ``` + */ (item: A): (self: readonly A[]) => A[]; + /** + * Adds an item to the start of the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.prepend([1, 2, 3, 4], 0); // [0, 1, 2, 3, 4] + * ``` + */ (self: readonly A[], item: A): A[]; } +/** + * Adds an item to the start of the given array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.prepend([1, 2, 3, 4], 0); // [0, 1, 2, 3, 4] + * ``` + */ export const prepend: Prepend = dual(2, (self: readonly any[], item: any) => [ item, ...self, ]); +/** + * Calls the specified callback function for all the elements in an array. + * The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3, 4, 5], A.reduce(0, (prev, current) => prev + current)); // 15 + * ``` + */ export function reduce( initialValue: B, reducer: (acc: B, current: A, index: number, array: readonly A[]) => B, @@ -236,6 +634,21 @@ export function reduce( return (self) => self.reduce(reducer, initialValue); } +/** + * Returns a new array with the elements of the original array sorted using the native sort algorithm + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const sortedNumbers = pipe([3, 5, 1, 2, 4], A.sort()); // [1, 2, 3, 4, 5] + * + * const sortedUsersByAge = pipe( + * [{ name: 'John', age: 29 }, { name: 'Fred', age: 20 }], + * A.sort((userA, userB) => userA.age - userB.age), + * ); // [{ name: 'Fred', age: 20 }, { name: 'John', age: 29 }] + * ``` + */ export function sort( compare?: (a: A, b: A) => number, ): (self: readonly A[]) => A[] { @@ -243,12 +656,57 @@ export function sort( } interface QuickSort { + /** + * Returns a new array with the elements of the original array sorted using the quickSort algorithm + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const sortedNumbers = pipe([3, 5, 1, 2, 4], A.quickSort()); // [1, 2, 3, 4, 5] + * + * const sortedUsersByAge = pipe( + * [{ name: 'John', age: 29 }, { name: 'Fred', age: 20 }], + * A.quickSort(user => user.age), + * ); // [{ name: 'Fred', age: 20 }, { name: 'John', age: 29 }] + * ``` + */ (getValue?: (item: A) => B): (self: readonly A[]) => A[]; + /** + * Returns a new array with the elements of the original array sorted using the quickSort algorithm + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const sortedNumbers = A.quickSort([3, 5, 1, 2, 4]); // [1, 2, 3, 4, 5] + * + * const sortedUsersByAge = A.quickSort([ + * { name: 'John', age: 29 }, + * { name: 'Fred', age: 20 }, + * ], user => user.age); // [{ name: 'Fred', age: 20 }, { name: 'John', age: 29 }] + * ``` + */ (self: readonly A[], getValue?: (item: A) => B): A[]; } +/** + * Returns a new array with the elements of the original array sorted using the quickSort algorithm + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const sortedNumbers = A.quickSort([3, 5, 1, 2, 4]); // [1, 2, 3, 4, 5] + * + * const sortedUsersByAge = A.quickSort([ + * { name: 'John', age: 29 }, + * { name: 'Fred', age: 20 }, + * ], user => user.age); // [{ name: 'Fred', age: 20 }, { name: 'John', age: 29 }] + * ``` + */ export const quickSort: QuickSort = (...args: any[]) => { - const implementation = dual( + const _quickSort = dual( 2, (self: readonly any[], getValue: (item: any) => any): any[] => { function _quickSortBy(array: any[]): any[] { @@ -285,16 +743,26 @@ export const quickSort: QuickSort = (...args: any[]) => { ); if (!args.length) { - return implementation(identity); + return _quickSort(identity); } if (args.length === 1 && Array.isArray(args[0])) { - return implementation(...args, identity); + return _quickSort(...args, identity); } - return implementation(...args); + return _quickSort(...args); }; +/** + * Returns a new array shuffling the elements of the original array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.shuffle([1, 2, 3, 4, 5]); // [2, 5, 1, 3, 4] + * ``` + */ export function shuffle(self: readonly A[]): A[] { function _shuffle(array: A[], left: number): A[] { const right = Math.floor(Math.random() * (left + 1)); @@ -307,6 +775,16 @@ export function shuffle(self: readonly A[]): A[] { return _shuffle([...self], self.length - 1); } +/** + * Returns a new array reversing the elements of the original array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const result = A.reverse([1, 2, 3]); // [3, 2, 1] + * ``` + */ export function reverse(self: readonly A[]): A[] { const reversed = [...self]; @@ -321,6 +799,16 @@ export function reverse(self: readonly A[]): A[] { return reversed; } +/** + * Adds all the elements of an array separated by the specified separator string + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.join(' | ')); // '1 | 2 | 3' + * ``` + */ export function join(separator?: string): (self: readonly A[]) => string { return (self) => self.join(separator); } @@ -329,9 +817,37 @@ export function join(separator?: string): (self: readonly A[]) => string { // filtering // ------------------------------------- +/** + * Returns the elements of an array that meet the condition specified in a callback function + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * type Positive = number; + * + * const result = pipe( + * [-2, -1, 0, 1, 2, 3], + * A.filter((number): number is Positive => number > 0), + * ); // [1, 2, 3] + * ``` + */ export function filter( refinement: (item: A, index: number, array: readonly A[]) => item is A2, ): (self: readonly A[]) => A2[]; +/** + * Returns the elements of an array that meet the condition specified in a callback function + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe( + * [-2, -1, 0, 1, 2, 3], + * A.filter(number => number > 0), + * ); // [1, 2, 3] + * ``` + */ export function filter( predicate: (item: A, index: number, array: readonly A[]) => boolean, ): (self: readonly A[]) => A[]; @@ -341,6 +857,22 @@ export function filter( return (self) => self.filter(predicate); } +/** + * Returns the mapped elements of an array, filtering out the None values + * + * @example + * ```ts + * import { A, O, N, pipe, flow } from 'funkcia'; + * + * const result = pipe( + * [-2, -1, 0, 1, 2, 3], + * A.filterMap(flow( + * O.fromPredicate(value => value > 0), + * O.map(N.multiply(5)), + * )), + * ); // [5, 10, 15] + * ``` + */ export function filterMap( callback: (item: A, index: number, array: readonly A[]) => Option, ): (self: readonly A[]) => B[] { @@ -371,9 +903,31 @@ export function filterMap( // asserting // ------------------------------------- +/** + * Determines whether all the members of an array satisfy the specified test + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * type Positive = number; + * + * const result = pipe([1, 2, 3], A.every((number): number is Positive => number > 0)); // true + * ``` + */ export function every( refinement: (item: A, index: number, array: readonly A[]) => item is A2, ): (self: readonly A[]) => self is A2[]; +/** + * Determines whether all the members of an array satisfy the specified test + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.every(number => number > 0)); // true + * ``` + */ export function every( predicate: (item: A, index: number, array: readonly A[]) => boolean, ): (self: readonly A[]) => boolean; @@ -383,12 +937,32 @@ export function every( return (self) => self.every(predicate); } +/** + * Determines whether the specified callback function returns true for any element of an array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.some(number => number > 0)); // true + * ``` + */ export function some( predicate: (item: A, index: number, array: readonly A[]) => boolean, ): (self: readonly A[]) => boolean { return (self) => self.some(predicate); } +/** + * Determines whether an array includes a certain element, returning true or false as appropriate + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const result = pipe([1, 2, 3], A.includes(1)); // true + * ``` + */ export function includes( searchElement: B extends string ? B | Omit : B, fromIndex?: number, @@ -400,44 +974,121 @@ export function includes( // composing // ------------------------------------- +/** + * Combines two or more arrays. This method returns a new array without modifying any existing arrays + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const numbers = pipe([1, 2, 3], A.concat([4, 5], [6, 7, 8])); // [1, 2, 3, 4, 5, 6, 7, 8] + * ``` + */ export function concat(...args: A[][]): (self: A[]) => A[] { return (self) => self.concat(...args); } interface Difference { + /** + * Creates a new array with only the values not included in the second array + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const existingMembers = [1, 2, 3, 4, 5]; + * const mostUpToDateMembers = [4, 5, 6, 7, 8]; + * + * const membersToRemove = pipe(existingMembers, A.difference(mostUpToDateMembers)); // [1, 2, 3] + * ``` + */ (second: A[]): (first: A[]) => A[]; + /** + * Creates a new array with only the values not included in the second array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const existingMembers = [1, 2, 3, 4, 5]; + * const mostUpToDateMembers = [4, 5, 6, 7, 8]; + * + * const membersToRemove = A.difference(existingMembers, mostUpToDateMembers); // [1, 2, 3] + * ``` + */ (first: A[], second: A[]): A[]; } +/** + * Creates a new array with only the values not included in the second array + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const existingMembers = [1, 2, 3, 4, 5]; + * const mostUpToDateMembers = [4, 5, 6, 7, 8]; + * + * const membersToRemove = A.difference(existingMembers, mostUpToDateMembers); // [1, 2, 3] + * ``` + */ export const difference: Difference = dual(2, (first: any[], second: any[]) => { - const firstSize = first.length; - const secondSize = second.length; - - if (!firstSize && !secondSize) { + if (!first.length) { return []; } - if (firstSize && !secondSize) { + if (!second.length) { return [...first]; } - if (!firstSize && secondSize) { - return [...second]; - } - - const firstSet = new Set(first); const secondSet = new Set(second); - - return first - .concat(second) - .filter((value) => !firstSet.has(value) || !secondSet.has(value)); + return first.filter((value) => !secondSet.has(value)); }); interface Intersection { + /** + * Returns a new array with only the common values in both arrays + * + * @example + * ```ts + * import { A, pipe } from 'funkcia'; + * + * const onlineUsers = [1, 2, 3, 4, 5]; + * const roomMembers = [2, 5, 7, 8, 9]; + * + * const onlineRoomMembers = pipe(onlineUsers, A.intersect(roomMembers)); // [2, 5] + * ``` + */ (second: A[]): (first: A[]) => A[]; + /** + * Returns a new array with only the common values in both arrays + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const onlineUsers = [1, 2, 3, 4, 5]; + * const roomMembers = [2, 5, 7, 8, 9]; + * + * const onlineRoomMembers = A.intersect(onlineUsers, roomMembers); // [2, 5] + * ``` + */ (first: A[], second: A[]): A[]; } +/** + * Returns a new array with only the common values in both arrays + * + * @example + * ```ts + * import { A } from 'funkcia'; + * + * const onlineUsers = [1, 2, 3, 4, 5]; + * const roomMembers = [2, 5, 7, 8, 9]; + * + * const onlineRoomMembers = A.intersect(onlineUsers, roomMembers); // [2, 5] + * ``` + */ export const intersection: Intersection = dual( 2, (first: any[], second: any[]) => { diff --git a/src/predicate.ts b/src/predicate.ts index bbf57ad..2e96075 100644 --- a/src/predicate.ts +++ b/src/predicate.ts @@ -2,6 +2,20 @@ export type Predicate = (a: A) => boolean; export type Refinement = (a: A) => a is B; +/** + * Returns a new function that will return the opposite boolean value of the original predicate. + * + * @example + * ```ts + * import { not } from 'funkcia'; + * + * const isGreaterThanZero = (value: number): boolean => value > 0; + * const isLessOrEqualToZero = not(isGreaterThanZero); + * + * const result = isLessOrEqualToZero(-1); + * //^? true + * ``` + */ export function not(predicate: Predicate): Predicate { return (a) => !predicate(a); }