diff --git a/.attw.json b/.attw.json index 068bad353..2112aa6a5 100644 --- a/.attw.json +++ b/.attw.json @@ -4,6 +4,7 @@ "includeEntrypoints": [ "array", "compat", + "fp", "function", "math", "object", diff --git a/.scripts/docs/generate-docs.mts b/.scripts/docs/generate-docs.mts index dbda636e6..95f4b273b 100644 --- a/.scripts/docs/generate-docs.mts +++ b/.scripts/docs/generate-docs.mts @@ -48,6 +48,14 @@ async function run() { ); await renderDocs(docsPaths, compatItems, { compat: true }); + + const fpItems = differenceWith( + toDocumentationItems(await doc(`file:${path.join(basePath, 'fp', 'index.ts')}`)), + items, + (x, y) => x.item.name === y.item.name + ); + + await renderDocs(docsPaths, fpItems, { fp: true }); } async function renderDocs(docsPaths: DocumentationPaths, items: DocumentationItems, options: RenderOptions = {}) { diff --git a/.scripts/docs/operations/render/en.ts b/.scripts/docs/operations/render/en.ts index 9e74cea77..e2e0dce35 100644 --- a/.scripts/docs/operations/render/en.ts +++ b/.scripts/docs/operations/render/en.ts @@ -2,7 +2,7 @@ import { RenderOptions } from './types.ts'; import { DocumentationItem } from '../../types/DocumentationItem.ts'; export function render(item: DocumentationItem, options: RenderOptions = {}) { - return [title(item.name), compatNotice(options), item.description, signature(item), examples(item)] + return [title(item.name), getNotice(options), item.description, signature(item), examples(item)] .filter(x => x != null) .join('\n\n'); } @@ -11,18 +11,28 @@ function title(name: string) { return `# ${name}`; } -function compatNotice(options: RenderOptions) { - if (!options.compat) { - return null; - } - - return ` +function getNotice(options: RenderOptions) { + if (options.compat) { + return ` ::: info This function is only available in \`es-toolkit/compat\` for compatibility reasons. It either has alternative native JavaScript APIs or isn’t fully optimized yet. When imported from \`es-toolkit/compat\`, it behaves exactly like lodash and provides the same functionalities, as detailed [here](../../../compatibility.md). ::: `.trim(); + } + + if (options.fp) { + return ` +::: info +This function can only be imported from \`es-toolkit/fp\`, which supports writing code in a functional programming style. + +When import code from \`es-toolkit/fp\`, you can use the pipe syntax or functions that automatically curry based on the number of input arguments. +::: +`.trim(); + } + + return null; } function signature(item: DocumentationItem) { diff --git a/.scripts/docs/operations/render/ja.ts b/.scripts/docs/operations/render/ja.ts index c9fc93518..7730a8174 100644 --- a/.scripts/docs/operations/render/ja.ts +++ b/.scripts/docs/operations/render/ja.ts @@ -2,7 +2,7 @@ import { RenderOptions } from './types.ts'; import { DocumentationItem } from '../../types/DocumentationItem.ts'; export function render(item: DocumentationItem, options: RenderOptions = {}) { - return [title(item.name), compatNotice(options), item.description, signature(item), examples(item)] + return [title(item.name), getNotice(options), item.description, signature(item), examples(item)] .filter(x => x != null) .join('\n\n'); } @@ -11,18 +11,28 @@ function title(name: string) { return `# ${name}`; } -function compatNotice(options: RenderOptions) { - if (!options.compat) { - return null; - } - - return ` +function getNotice(options: RenderOptions) { + if (options.compat) { + return ` ::: info この関数は互換性のために \`es-toolkit/compat\` からのみインポートできます。代替可能なネイティブ JavaScript API があるか、まだ十分に最適化されていないためです。 \`es-toolkit/compat\` からこの関数をインポートすると、[lodash と完全に同じように動作](../../../compatibility.md)します。 ::: `.trim(); + } + + if (options.fp) { + return ` +::: info +この関数は関数型プログラミングスタイルのコードを書くために \`es-toolkit/fp\` からのみインポートできます。 + +\`es-toolkit/fp\` から関数をインポートすると、パイプ文法を使用したり、引数の数に応じて自動的に curry 化される関数を使用することができます。 +::: +`.trim(); + } + + return null; } function signature(item: DocumentationItem) { diff --git a/.scripts/docs/operations/render/ko.ts b/.scripts/docs/operations/render/ko.ts index 2de4384f8..197d61313 100644 --- a/.scripts/docs/operations/render/ko.ts +++ b/.scripts/docs/operations/render/ko.ts @@ -2,7 +2,7 @@ import { RenderOptions } from './types.ts'; import { DocumentationItem } from '../../types/DocumentationItem.ts'; export function render(item: DocumentationItem, options: RenderOptions = {}) { - return [title(item.name), compatNotice(options), item.description, signature(item), examples(item)] + return [title(item.name), getNotice(options), item.description, signature(item), examples(item)] .filter(x => x != null) .join('\n\n'); } @@ -11,18 +11,28 @@ function title(name: string) { return `# ${name}`; } -function compatNotice(options: RenderOptions) { - if (!options.compat) { - return null; - } - - return ` +function getNotice(options: RenderOptions) { + if (options.compat) { + return ` ::: info 이 함수는 호환성을 위한 \`es-toolkit/compat\` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요. \`es-toolkit/compat\`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요. ::: `.trim(); + } + + if (options.fp) { + return ` +::: info +이 함수는 함수형 프로그래밍 방식의 코드 작성을 지원하기 위한 \`es-toolkit/fp\` 에서만 가져올 수 있어요. + +\`es-toolkit/fp\`에서 함수를 가져오면 pipe 문법을 사용하거나 인자 입력 갯수에 따라 자동으로 커링되는 함수를 사용할 수 있어요. +::: +`.trim(); + } + + return null; } function signature(item: DocumentationItem) { diff --git a/.scripts/docs/operations/render/types.ts b/.scripts/docs/operations/render/types.ts index cb727ae28..c30fa415a 100644 --- a/.scripts/docs/operations/render/types.ts +++ b/.scripts/docs/operations/render/types.ts @@ -1,3 +1,4 @@ export interface RenderOptions { compat?: boolean; + fp?: boolean; } diff --git a/.scripts/docs/operations/render/zh_hans.ts b/.scripts/docs/operations/render/zh_hans.ts index f388effcf..5e00647d7 100644 --- a/.scripts/docs/operations/render/zh_hans.ts +++ b/.scripts/docs/operations/render/zh_hans.ts @@ -2,7 +2,7 @@ import { RenderOptions } from './types.ts'; import { DocumentationItem } from '../../types/DocumentationItem.ts'; export function render(item: DocumentationItem, options: RenderOptions = {}) { - return [title(item.name), compatNotice(options), item.description, signature(item), examples(item)] + return [title(item.name), getNotice(options), item.description, signature(item), examples(item)] .filter(x => x != null) .join('\n\n'); } @@ -11,18 +11,28 @@ function title(name: string) { return `# ${name}`; } -function compatNotice(options: RenderOptions) { - if (!options.compat) { - return null; - } - - return ` +function getNotice(options: RenderOptions) { + if (options.compat) { + return ` ::: info 出于兼容性原因,此函数仅在 \`es-toolkit/compat\` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。 从 \`es-toolkit/compat\` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。 ::: `.trim(); + } + + if (options.fp) { + return ` +::: info +这个函数只能从 \`es-toolkit/fp\` 导入,以支持函数式编程风格的代码编写。 + +从 \`es-toolkit/fp\` 导入函数后,可以使用管道语法或根据输入参数数量自动 curry 化的函数。 +::: +`.trim(); + } + + return null; } function signature(item: DocumentationItem) { diff --git a/.scripts/fp/_internal/getter/functionDeclaration.ts b/.scripts/fp/_internal/getter/functionDeclaration.ts new file mode 100644 index 000000000..1ee0bd263 --- /dev/null +++ b/.scripts/fp/_internal/getter/functionDeclaration.ts @@ -0,0 +1,46 @@ +import { CommentKind } from 'ast-types/gen/kinds'; +import { + FunctionDeclaration, + Identifier, + JSCodeshift, + TSTypeAnnotation, + TSTypeParameterDeclaration, + TypeParameterDeclaration, +} from 'jscodeshift'; + +export function getFunctionDeclaration( + { + functionName, + typeParameters, + params, + returnType, + }: { + functionName: string; + typeParameters: TypeParameterDeclaration | TSTypeParameterDeclaration | null | undefined; + params: { + name: string[]; + type: TSTypeAnnotation[]; + }; + returnType: TSTypeAnnotation; + }, + j: JSCodeshift +) { + const newFunctionDeclaration = j.template.statement([ + ` + export function ${functionName}(${params.name.join(', ')});\n\n + `, + ]) as { + comments?: CommentKind[]; + declaration: Omit & { + params: Identifier[]; + }; + }; + + newFunctionDeclaration.declaration.typeParameters = typeParameters; + params.type.forEach((type, idx) => { + newFunctionDeclaration.declaration.params[idx].typeAnnotation = type; + }); + newFunctionDeclaration.declaration.returnType = returnType; + + return newFunctionDeclaration; +} diff --git a/.scripts/fp/_internal/getter/typedParam.ts b/.scripts/fp/_internal/getter/typedParam.ts new file mode 100644 index 000000000..64685319a --- /dev/null +++ b/.scripts/fp/_internal/getter/typedParam.ts @@ -0,0 +1,8 @@ +import { JSCodeshift } from 'jscodeshift'; +import { Param } from '../types'; + +export function getTypedParam({ name, type }: Param, j: JSCodeshift) { + const param = j.identifier(name); + param.typeAnnotation = j.tsTypeAnnotation(type); + return param; +} diff --git a/.scripts/fp/_internal/types.ts b/.scripts/fp/_internal/types.ts new file mode 100644 index 000000000..c1fb13951 --- /dev/null +++ b/.scripts/fp/_internal/types.ts @@ -0,0 +1,6 @@ +import { TSTypeKind } from 'ast-types/gen/kinds'; + +export type Param = { + name: string; + type: TSTypeKind; +}; diff --git a/.scripts/fp/_internal/validator/functionDeclaration.ts b/.scripts/fp/_internal/validator/functionDeclaration.ts new file mode 100644 index 000000000..fb957b71a --- /dev/null +++ b/.scripts/fp/_internal/validator/functionDeclaration.ts @@ -0,0 +1,15 @@ +import { Declaration, FunctionDeclaration, Identifier } from 'jscodeshift'; +import { namedTypes as n } from 'ast-types'; + +export function isValidFunctionDeclaration(functionDeclaration: Declaration | null): functionDeclaration is Omit< + FunctionDeclaration, + 'id' +> & { + id: Identifier; +} { + return ( + n.FunctionDeclaration.check(functionDeclaration) && + functionDeclaration.id != null && + n.TSTypeAnnotation.check(functionDeclaration.returnType) + ); +} diff --git a/.scripts/fp/_internal/validator/params.ts b/.scripts/fp/_internal/validator/params.ts new file mode 100644 index 000000000..7948808bf --- /dev/null +++ b/.scripts/fp/_internal/validator/params.ts @@ -0,0 +1,13 @@ +import { Identifier, Pattern, TSTypeAnnotation } from 'jscodeshift'; +import { namedTypes as n } from 'ast-types'; + +export function isValidParams( + params: Pattern[] +): params is (Omit & { typeAnnotation: TSTypeAnnotation })[] { + return params.every( + param => + n.Identifier.check(param) && + n.TSTypeAnnotation.check(param.typeAnnotation) && + !n.TSTypeAnnotation.check(param.typeAnnotation.typeAnnotation) + ); +} diff --git a/.scripts/fp/create-fp-version.sh b/.scripts/fp/create-fp-version.sh new file mode 100644 index 000000000..f63a17627 --- /dev/null +++ b/.scripts/fp/create-fp-version.sh @@ -0,0 +1,9 @@ +FUNC=$1 + +find ./src -name "$1.ts" -maxdepth 2 -exec sh -c 'mkdir -p "./src/fp/$(dirname "{}" | sed "s|^./src/||")" && cp "{}" "./src/fp/$(dirname "{}" | sed "s|^./src/||")/"' \; +find ./src -name "$1.spec.ts" -maxdepth 2 -exec sh -c 'mkdir -p "./src/fp/$(dirname "{}" | sed "s|^./src/||")" && cp "{}" "./src/fp/$(dirname "{}" | sed "s|^./src/||")/"' \; +FP_FILENAME=$(find ./src/fp -name "$1.ts") +FP_SPEC_FILENAME=$(find ./src/fp -name "$1.spec.ts") +yarn jscodeshift -t ./.scripts/fp/toPipable.ts $FP_FILENAME +yarn jscodeshift -t ./.scripts/fp/toPipableSpec.ts $FP_SPEC_FILENAME +yarn prettier --write $FP_FILENAME \ No newline at end of file diff --git a/.scripts/fp/toPipable.ts b/.scripts/fp/toPipable.ts new file mode 100644 index 000000000..00b765d04 --- /dev/null +++ b/.scripts/fp/toPipable.ts @@ -0,0 +1,179 @@ +import { TSTypeKind } from 'ast-types/gen/kinds'; +import { API, FileInfo, TSTypeAnnotation } from 'jscodeshift'; +import { getFunctionDeclaration } from './_internal/getter/functionDeclaration'; +import { getTypedParam } from './_internal/getter/typedParam'; +import { Param } from './_internal/types'; +import { isValidFunctionDeclaration } from './_internal/validator/functionDeclaration'; +import { isValidParams } from './_internal/validator/params'; + +export const parser = 'tsx'; + +export default function transformer(file: FileInfo, api: API) { + const j = api.jscodeshift; + + const categoryRegResult = /fp\/([a-z]+)/.exec(file.path); + const category = categoryRegResult ? categoryRegResult[1] : ''; + + j.FunctionDeclaration; + return j(file.source) + .find(j.ExportNamedDeclaration) + .forEach(path => { + const comments = [...(path.value.comments ?? [])]; + const comment = comments[0].value; + const functionDeclaration = path.value.declaration; + + if (!isValidFunctionDeclaration(functionDeclaration)) { + return; + } + + const functionName = functionDeclaration.id.name; + + const params = functionDeclaration.params; + + if (!isValidParams(params)) { + return; + } + + const originParams: Record<'first' | 'second', Param> = { + first: { + name: params[0].name, + type: params[0].typeAnnotation.typeAnnotation as TSTypeKind, + }, + second: { + name: params[1].name, + type: params[1].typeAnnotation.typeAnnotation as TSTypeKind, + }, + }; + + const newFirstArgumentName = `${originParams.first.name}Or${originParams.second.name.replace(/./, first => first.toUpperCase())}`; + + const firstParam = getTypedParam(originParams.first, j); + + const selfCurringStatement = j.returnStatement( + j.arrowFunctionExpression( + [firstParam], + j.callExpression(j.identifier(functionName), [ + j.identifier(params[0].name), + j.tsAsExpression(j.identifier(newFirstArgumentName), originParams.second.type), + ]) + ) + ); + + const curringConditionalStatement = j.blockStatement([ + j.ifStatement( + j.binaryExpression('==', j.identifier(originParams.second.name), j.literal(null)), + j.blockStatement([selfCurringStatement]) + ), + j.variableDeclaration('const', [ + j.variableDeclarator( + j.identifier(params[0].name), + j.tsAsExpression(j.identifier(newFirstArgumentName), originParams.first.type) + ), + ]), + ]); + + const referrerCallStatement = j.returnStatement( + j.callExpression(j.identifier(`${functionName}Toolkit`), [ + j.identifier(originParams.first.name), + j.identifier(originParams.second.name), + ]) + ); + + params[0].name = newFirstArgumentName; + params[0].typeAnnotation = j.tsTypeAnnotation( + j.tsUnionType([originParams.first.type, originParams.second.type].map(j.tsParenthesizedType)) + ); + params[1].name = `${originParams.second.name}?`; + + functionDeclaration.body = j.blockStatement([...curringConditionalStatement.body, referrerCallStatement]); + + const nonCurriedDeclaration = getFunctionDeclaration( + { + functionName, + typeParameters: functionDeclaration.typeParameters, + params: { + name: [originParams.first.name, originParams.second.name], + type: [originParams.first.type, originParams.second.type].map(j.tsTypeAnnotation), + }, + returnType: functionDeclaration.returnType as TSTypeAnnotation, + }, + j + ); + + nonCurriedDeclaration.comments = [j.commentBlock(comments[0].value, true)]; + + const returnOfCurried = j.tsFunctionType([getTypedParam(originParams.first, j)]); + returnOfCurried.typeAnnotation = functionDeclaration.returnType as TSTypeAnnotation; + + const curriedDeclaration = getFunctionDeclaration( + { + functionName, + typeParameters: functionDeclaration.typeParameters, + params: { + name: [originParams.second.name], + type: [j.tsTypeAnnotation(originParams.second.type)], + }, + returnType: j.tsTypeAnnotation(returnOfCurried), + }, + j + ); + + const firstParamLine = comment.split('\n').find(line => line.startsWith(' * @param')) ?? ''; + + const firstParamRegResult = /{([^}]+)} ([^\s]+)/.exec(firstParamLine); + + if (firstParamRegResult == null) { + curriedDeclaration.comments = [j.commentBlock(comment, true)]; + } else { + const [_, typeName, paramName] = firstParamRegResult; + + const exampleComments = comment.split('@example')[1]; + + const usageExampleLine = exampleComments + .split('\n') + .find(comment => new RegExp(`${functionName}\(.+\);`).test(comment)); + + const curriedComment = + usageExampleLine == null + ? comment + : comment.replace( + usageExampleLine, + usageExampleLine.replace( + new RegExp(`${functionName}\\(([^,]+), (.+)\\);`), + (_, firstParam, secondParam) => `${functionName}(${String(secondParam).trim()})(${firstParam});` + ) + ); + + curriedDeclaration.comments = [ + j.commentBlock( + curriedComment.replace(`${firstParamLine}\n`, '').replace( + /@returns {([^}]+)} (.+)/, + (_, returnType, note) => + `@returns {(${paramName}: ${typeName}) => ${returnType}} A function that receive ${firstParamLine + .split('-')[1] + .trim() + .replace(/\.$/, '') + .replace(/^./, firstLetter => firstLetter.toLowerCase())} as argument and returns ${String(note) + .replace(/\.$/, '') + .replace(/^./, firstLetter => firstLetter.toLowerCase())}.` + ), + true + ), + ]; + } + + path.value.comments = null; + + functionDeclaration.returnType = undefined; + + const referrerImportDeclaration = j.importDeclaration( + [j.importSpecifier(j.identifier(functionName), j.identifier(`${functionName}Toolkit`))], + j.literal(`../../${category}/${functionName}`) + ); + + j(path).insertBefore(referrerImportDeclaration); + j(path).insertBefore(nonCurriedDeclaration); + j(path).insertBefore(curriedDeclaration); + }) + .toSource(); +} diff --git a/.scripts/fp/toPipableSpec.ts b/.scripts/fp/toPipableSpec.ts new file mode 100644 index 000000000..9fe85c058 --- /dev/null +++ b/.scripts/fp/toPipableSpec.ts @@ -0,0 +1,67 @@ +import { namedTypes as n } from 'ast-types'; +import { TSTypeKind } from 'ast-types/gen/kinds'; +import { API, CallExpression, FileInfo, Identifier, StringLiteral, TSTypeAnnotation } from 'jscodeshift'; +import { getFunctionDeclaration } from './_internal/getter/functionDeclaration'; +import { getTypedParam } from './_internal/getter/typedParam'; +import { Param } from './_internal/types'; +import { isValidFunctionDeclaration } from './_internal/validator/functionDeclaration'; +import { isValidParams } from './_internal/validator/params'; +import { cloneDeep } from '../../src/compat'; + +export const parser = 'tsx'; + +export default function transformer(file: FileInfo, api: API) { + const j = api.jscodeshift; + + const categoryRegResult = /fp\/([a-zA-Z]+)\/([a-zA-Z]+)/.exec(file.path); + const fileName = categoryRegResult ? categoryRegResult[2] : ''; + + j.FunctionDeclaration; + const firstTouched = j(file.source) + .find(j.ExpressionStatement, node => { + return ( + n.CallExpression.check(node.expression) && + n.Identifier.check(node.expression.callee) && + node.expression.callee.name === 'it' + ); + }) + .forEach(path => { + const expression = path.value.expression as CallExpression; + const firstArgument = expression.arguments[0] as StringLiteral; + j(path).insertBefore( + j.expressionStatement( + j.callExpression(j.identifier('it'), [ + j.stringLiteral(`(non-curried) ${firstArgument.value}`), + expression.arguments[1], + ]) + ) + ); + + firstArgument.value = `(curried) ${firstArgument.value}`; + }) + .toSource(); + + return j(firstTouched) + .find( + j.CallExpression, + node => + n.Identifier.check(node.callee) && + n.StringLiteral.check(node.arguments[0]) && + node.callee.name === 'it' && + node.arguments[0].value.includes('(curried)') + ) + .forEach(path => { + j(path) + .find(j.CallExpression, { + callee: { name: fileName }, + }) + .forEach(omitCall => { + const [firstParameter, secondParameter] = omitCall.value.arguments; + + omitCall.value.arguments = [secondParameter]; + const curried = j.callExpression(omitCall.value, [firstParameter]); + omitCall.replace(curried); + }); + }) + .toSource(); +} diff --git a/.scripts/postbuild.sh b/.scripts/postbuild.sh index 8ccb5f9db..256c95a0f 100755 --- a/.scripts/postbuild.sh +++ b/.scripts/postbuild.sh @@ -6,6 +6,7 @@ echo "export * from './dist/array';" > array.d.ts echo "export * from './dist/error';" > error.d.ts echo "export * from './dist/compat';" > compat.d.ts +echo "export * from './dist/fp';" > fp.d.ts echo "export * from './dist/function';" > function.d.ts echo "export * from './dist/math';" > math.d.ts echo "export * from './dist/object';" > object.d.ts diff --git a/benchmarks/package.json b/benchmarks/package.json index 78c7e7154..50383f4f7 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -9,11 +9,13 @@ "esbuild": "0.23.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", + "ramda": "^0.30.1", "vitest": "^2.1.2" }, "devDependencies": { "@types/lodash": "^4", "@types/lodash-es": "^4", + "@types/ramda": "^0", "rfdc": "^1.4.1" } } diff --git a/benchmarks/performance/pipe.bench.ts b/benchmarks/performance/pipe.bench.ts new file mode 100644 index 000000000..f04eb5f48 --- /dev/null +++ b/benchmarks/performance/pipe.bench.ts @@ -0,0 +1,44 @@ +import { bench, describe } from 'vitest'; +import { pipe as pipeToolkit_ } from 'es-toolkit/fp'; +import { pipe as pipeLodash_ } from 'lodash/fp'; +import { pipe as pipeRamda_ } from 'ramda'; + +const pipeToolkit = pipeToolkit_; +const pipeLodash = pipeLodash_; +const pipeRamda = pipeRamda_; + +function toString(value) { + return String(value); +} + +function curriedMap(mapper: (value: T) => R) { + return (arr: T[]) => { + const result: R[] = []; + + for (const item of arr) { + result.push(mapper(item)); + } + + return result; + }; +} + +function double(arr: T[]) { + const result = [...arr, ...arr]; + + return result; +} + +describe('pipe', () => { + bench('es-toolkit/fp/pipe', () => { + pipeToolkit([1, 2, 3], curriedMap(toString), double); + }); + + bench('lodash/fp/pipe', () => { + pipeLodash(curriedMap(toString), double)([1, 2, 3]); + }); + + bench('ramda/pipe', () => { + pipeRamda(curriedMap(toString), double)([1, 2, 3]); + }); +}); diff --git a/docs/.vitepress/en.mts b/docs/.vitepress/en.mts index cbc5602fd..ce3c22665 100644 --- a/docs/.vitepress/en.mts +++ b/docs/.vitepress/en.mts @@ -55,6 +55,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'array'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'array'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'array'), ], }, { @@ -62,6 +63,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'function'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'function'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'function'), ], }, { @@ -69,6 +71,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'math'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'math'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'math'), ], }, { @@ -76,6 +79,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'object'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'object'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'object'), ], }, { @@ -83,6 +87,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'predicate'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'predicate'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'predicate'), ], }, { @@ -90,6 +95,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'promise'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'promise'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'promise'), ], }, { @@ -97,6 +103,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'string'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'string'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'string'), ], }, { @@ -104,6 +111,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'util'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'util'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'util'), ], }, { @@ -111,6 +119,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'reference', 'error'), ...getSidebarItems.compat('en', docsRoot, 'reference', 'compat', 'error'), + ...getSidebarItems.fp('en', docsRoot, 'reference', 'fp', 'error'), ], }, ]), diff --git a/docs/.vitepress/ja.mts b/docs/.vitepress/ja.mts index 95cd3f935..31491588c 100644 --- a/docs/.vitepress/ja.mts +++ b/docs/.vitepress/ja.mts @@ -54,6 +54,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'array'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'array'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'array'), ], }, { @@ -61,6 +62,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'function'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'function'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'function'), ], }, { @@ -68,6 +70,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'math'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'math'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'math'), ], }, { @@ -75,6 +78,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'object'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'object'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'object'), ], }, { @@ -82,6 +86,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'predicate'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'predicate'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'predicate'), ], }, { @@ -89,6 +94,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'promise'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'promise'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'promise'), ], }, { @@ -96,6 +102,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'string'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'string'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'string'), ], }, { @@ -103,6 +110,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'util'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'util'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'util'), ], }, { @@ -110,6 +118,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ja', 'reference', 'error'), ...getSidebarItems.compat('ja', docsRoot, 'ja', 'reference', 'compat', 'error'), + ...getSidebarItems.fp('ja', docsRoot, 'ja', 'reference', 'fp', 'error'), ], }, ]), diff --git a/docs/.vitepress/ko.mts b/docs/.vitepress/ko.mts index 2db47d5a9..7525ace21 100644 --- a/docs/.vitepress/ko.mts +++ b/docs/.vitepress/ko.mts @@ -54,6 +54,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'array'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'array'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'array'), ], }, { @@ -61,6 +62,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'function'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'function'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'function'), ], }, { @@ -68,6 +70,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'math'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'math'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'math'), ], }, { @@ -75,6 +78,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'object'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'object'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'object'), ], }, { @@ -82,6 +86,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'predicate'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'predicate'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'predicate'), ], }, { @@ -89,6 +94,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'promise'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'promise'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'array'), ], }, { @@ -96,6 +102,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'string'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'string'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'string'), ], }, { @@ -103,6 +110,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'util'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'util'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'util'), ], }, { @@ -110,6 +118,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'ko', 'reference', 'error'), ...getSidebarItems.compat('ko', docsRoot, 'ko', 'reference', 'compat', 'error'), + ...getSidebarItems.fp('ko', docsRoot, 'ko', 'reference', 'fp', 'error'), ], }, ]), diff --git a/docs/.vitepress/libs/getSidebarItems.mts b/docs/.vitepress/libs/getSidebarItems.mts index a5c78f1d5..a7328a819 100644 --- a/docs/.vitepress/libs/getSidebarItems.mts +++ b/docs/.vitepress/libs/getSidebarItems.mts @@ -35,3 +35,23 @@ getSidebarItems.compat = function (locale: 'en' | 'ko' | 'ja' | 'zh_hans', docsR }; }); }; + +getSidebarItems.fp = function (locale: 'en' | 'ko' | 'ja' | 'zh_hans', docsRoot: string, ...paths: string[]) { + return getSidebarItems(docsRoot, ...paths).map(item => { + const fpStr = + locale === 'en' + ? '(fp)' + : locale === 'ko' + ? '(함수형)' + : locale === 'ja' + ? '(関数型プログラミング)' + : locale === 'zh_hans' + ? '(函数式编程)' + : ''; + + return { + ...item, + text: `${item.text} ${fpStr}`, + }; + }); +}; diff --git a/docs/.vitepress/theme/index.css b/docs/.vitepress/theme/index.css index 55d7a65b0..1504cf1eb 100644 --- a/docs/.vitepress/theme/index.css +++ b/docs/.vitepress/theme/index.css @@ -7,3 +7,7 @@ :root[lang='ko'] { word-break: keep-all; } + +#signature + div > pre.vp-code { + max-height: 500px; +} diff --git a/docs/.vitepress/zh_hans.mts b/docs/.vitepress/zh_hans.mts index 2443936ef..8b2a623db 100644 --- a/docs/.vitepress/zh_hans.mts +++ b/docs/.vitepress/zh_hans.mts @@ -55,6 +55,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'array'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'array'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'array'), ], }, { @@ -62,6 +63,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'function'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'function'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'function'), ], }, { @@ -69,6 +71,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'math'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'math'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'math'), ], }, { @@ -76,6 +79,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'object'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'object'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'object'), ], }, { @@ -83,6 +87,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'predicate'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'predicate'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'predicate'), ], }, { @@ -90,6 +95,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'promise'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'promise'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'promise'), ], }, { @@ -97,6 +103,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'string'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'string'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'string'), ], }, { @@ -104,6 +111,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'util'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'util'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'util'), ], }, { @@ -111,6 +119,7 @@ function sidebar(): DefaultTheme.Sidebar { items: [ ...getSidebarItems(docsRoot, 'zh_hans', 'reference', 'error'), ...getSidebarItems.compat('zh_hans', docsRoot, 'zh_hans', 'reference', 'compat', 'error'), + ...getSidebarItems.fp('zh_hans', docsRoot, 'zh_hans', 'reference', 'fp', 'error'), ], }, ]), diff --git a/docs/ja/reference/fp/core/pipe.md b/docs/ja/reference/fp/core/pipe.md new file mode 100644 index 000000000..f0eec2196 --- /dev/null +++ b/docs/ja/reference/fp/core/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +この関数は関数型プログラミングスタイルのコードを書くために `es-toolkit/fp` からのみインポートできます。 + +`es-toolkit/fp` から関数をインポートすると、パイプ文法を使用したり、引数の数に応じて自動的に curry 化される関数を使用することができます。 +::: + +パイプを通過しながら値を処理します。複数のステップで値を加工するコードを宣言的に記述する際に便利です。 + +加工したい初期値を `pipe` 関数に第一引数として渡し、第二引数以降には値を加工する関数を順番に渡してください。こうすることで、`pipe` 関数の最初の関数は初期値をパラメータとして受け取り、残りの関数は前の関数が返す値を受け取って順番に実行され、最後の関数まで実行されます。 + +もし初期値が Promise の値であったり、値を処理する過程で非同期関数が存在する場合、`pipe` 関数は値を非同期的に処理して `Promise` を返します。 + +## インターフェース + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### パラメータ + +- `initial` (`I`): 処理される初期値です。 +- `fn1 ~ fn` (`F1 ~ F{n}`): 順番に値を処理する関数たちです。各関数は前の関数の返り値をパラメータとして受け取って実行されます。関数は最大で18個まで渡すことができます。 + +## 戻り値 + +(`PipeResult`): 処理された結果値、つまり最後の関数の返り値です。もし初期値が `Promise` であったり、中間に非同期関数がある場合、`pipe` 関数は `Promise` の値を返します。 + +## 例 + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// 非同期関数と一緒に使用できます。 +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// `curry` 化された関数と一緒に使用できます。 +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/docs/ja/reference/fp/function/pipe.md b/docs/ja/reference/fp/function/pipe.md new file mode 100644 index 000000000..f0eec2196 --- /dev/null +++ b/docs/ja/reference/fp/function/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +この関数は関数型プログラミングスタイルのコードを書くために `es-toolkit/fp` からのみインポートできます。 + +`es-toolkit/fp` から関数をインポートすると、パイプ文法を使用したり、引数の数に応じて自動的に curry 化される関数を使用することができます。 +::: + +パイプを通過しながら値を処理します。複数のステップで値を加工するコードを宣言的に記述する際に便利です。 + +加工したい初期値を `pipe` 関数に第一引数として渡し、第二引数以降には値を加工する関数を順番に渡してください。こうすることで、`pipe` 関数の最初の関数は初期値をパラメータとして受け取り、残りの関数は前の関数が返す値を受け取って順番に実行され、最後の関数まで実行されます。 + +もし初期値が Promise の値であったり、値を処理する過程で非同期関数が存在する場合、`pipe` 関数は値を非同期的に処理して `Promise` を返します。 + +## インターフェース + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### パラメータ + +- `initial` (`I`): 処理される初期値です。 +- `fn1 ~ fn` (`F1 ~ F{n}`): 順番に値を処理する関数たちです。各関数は前の関数の返り値をパラメータとして受け取って実行されます。関数は最大で18個まで渡すことができます。 + +## 戻り値 + +(`PipeResult`): 処理された結果値、つまり最後の関数の返り値です。もし初期値が `Promise` であったり、中間に非同期関数がある場合、`pipe` 関数は `Promise` の値を返します。 + +## 例 + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// 非同期関数と一緒に使用できます。 +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// `curry` 化された関数と一緒に使用できます。 +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/docs/ko/reference/fp/core/pipe.md b/docs/ko/reference/fp/core/pipe.md new file mode 100644 index 000000000..d6f264b71 --- /dev/null +++ b/docs/ko/reference/fp/core/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +이 함수는 함수형 프로그래밍 방식의 코드 작성을 지원하기 위한 `es-toolkit/fp` 에서만 가져올 수 있어요. + +`es-toolkit/fp`에서 함수를 가져오면 pipe 문법을 사용하거나 인자 입력 갯수에 따라 자동으로 커링되는 함수를 사용할 수 있어요. +::: + +파이프를 통과하면서 값을 처리해요. 여러 단계에 걸쳐 값을 가공하는 코드를 선언적으로 작성할 때 유용해요. + +가공하고자 하는 초기값을 `pipe` 함수에 첫번째 인자값으로 전달하고, 두번째부터는 값을 가공할 함수를 순서대로 전달하세요. 이렇게 하면 `pipe` 함첫 번째 함수는 초기값을 파라미터로 받고, 나머지 함수들은 앞선 함수가 반환하는 값을 받아 순서대로 실행되는 형태로 마지막 함수까지 실행할 수 있어요. + +만약 초기값이 `Promise` 값이거나, 값을 처리하는 과정에서 비동기 함수가 존재하면, `pipe` 함수가 값을 비동기적으로 처리해서 `Promise`를 반환해요. + +## 인터페이스 + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### 파라미터 + +- `initial` (`I`): 처리될 초기 값이에요. +- `fn1 ~ fn` (`F1 ~ F{n}`): 순서대로 값을 처리할 함수들이에요. 각 함수는 이전 함수의 반환값을 파라미터로 전달 받아 실행돼요. 함수들은 최대 18개까지 전달할 수 있어요. + +## 반환 값 + +(`PipeResult`): 처리된 결과 값, 즉 마지막 함수의 반환값이에요. 만약 초기 값이 `Promise`이거나 중간에 비동기 함수가 있다면, `pipe` 함수는 `Promise` 값을 반환해요. + +## 예시 + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// 비동기 함수와 함께 사용할 수 있어요. +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// 커링된 함수와 함께 사용할 수 있어요. +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/docs/ko/reference/fp/function/pipe.md b/docs/ko/reference/fp/function/pipe.md new file mode 100644 index 000000000..d6f264b71 --- /dev/null +++ b/docs/ko/reference/fp/function/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +이 함수는 함수형 프로그래밍 방식의 코드 작성을 지원하기 위한 `es-toolkit/fp` 에서만 가져올 수 있어요. + +`es-toolkit/fp`에서 함수를 가져오면 pipe 문법을 사용하거나 인자 입력 갯수에 따라 자동으로 커링되는 함수를 사용할 수 있어요. +::: + +파이프를 통과하면서 값을 처리해요. 여러 단계에 걸쳐 값을 가공하는 코드를 선언적으로 작성할 때 유용해요. + +가공하고자 하는 초기값을 `pipe` 함수에 첫번째 인자값으로 전달하고, 두번째부터는 값을 가공할 함수를 순서대로 전달하세요. 이렇게 하면 `pipe` 함첫 번째 함수는 초기값을 파라미터로 받고, 나머지 함수들은 앞선 함수가 반환하는 값을 받아 순서대로 실행되는 형태로 마지막 함수까지 실행할 수 있어요. + +만약 초기값이 `Promise` 값이거나, 값을 처리하는 과정에서 비동기 함수가 존재하면, `pipe` 함수가 값을 비동기적으로 처리해서 `Promise`를 반환해요. + +## 인터페이스 + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### 파라미터 + +- `initial` (`I`): 처리될 초기 값이에요. +- `fn1 ~ fn` (`F1 ~ F{n}`): 순서대로 값을 처리할 함수들이에요. 각 함수는 이전 함수의 반환값을 파라미터로 전달 받아 실행돼요. 함수들은 최대 18개까지 전달할 수 있어요. + +## 반환 값 + +(`PipeResult`): 처리된 결과 값, 즉 마지막 함수의 반환값이에요. 만약 초기 값이 `Promise`이거나 중간에 비동기 함수가 있다면, `pipe` 함수는 `Promise` 값을 반환해요. + +## 예시 + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// 비동기 함수와 함께 사용할 수 있어요. +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// 커링된 함수와 함께 사용할 수 있어요. +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/docs/reference/fp/core/pipe.md b/docs/reference/fp/core/pipe.md new file mode 100644 index 000000000..f1038cfd6 --- /dev/null +++ b/docs/reference/fp/core/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +This function can only be imported from `es-toolkit/fp`, which supports writing code in a functional programming style. + +When import code from `es-toolkit/fp`, you can use the pipe syntax or functions that automatically curry based on the number of input arguments. +::: + +Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + +Pass the initial value you want to process as the first argument to the `pipe` function, and from the second argument onward, provide the functions that process the value in order. This way, the first function receives the initial value as a parameter, and the remaining functions receive the return value of the previous function, executing sequentially all the way to the last function. + +If the initial value is a `Promise` or if there are async functions in the processing chain, the `pipe` function will handle the value asynchronously and return a `Promise`. + +## Signature + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### Parameters + +- `initial` (`I`): The initial value to be processed. +- `fn1 ~ fn` (`F1 ~ F{n}`): The functions that are called in order during the value processing. Each function receives the return value of the previous function as its parameter. the maximum number of functions is 18 + +## Returns + +(`PipeResult`): Processed result value, specifically the return value of the last function. If the initial value is a `Promise` or if there are async functions in the middle, the `pipe` function will return a `Promise` value. + +## Examples + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// Use pipe with async function +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// Use pipe with curried function +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/docs/reference/fp/function/pipe.md b/docs/reference/fp/function/pipe.md new file mode 100644 index 000000000..f1038cfd6 --- /dev/null +++ b/docs/reference/fp/function/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +This function can only be imported from `es-toolkit/fp`, which supports writing code in a functional programming style. + +When import code from `es-toolkit/fp`, you can use the pipe syntax or functions that automatically curry based on the number of input arguments. +::: + +Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + +Pass the initial value you want to process as the first argument to the `pipe` function, and from the second argument onward, provide the functions that process the value in order. This way, the first function receives the initial value as a parameter, and the remaining functions receive the return value of the previous function, executing sequentially all the way to the last function. + +If the initial value is a `Promise` or if there are async functions in the processing chain, the `pipe` function will handle the value asynchronously and return a `Promise`. + +## Signature + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### Parameters + +- `initial` (`I`): The initial value to be processed. +- `fn1 ~ fn` (`F1 ~ F{n}`): The functions that are called in order during the value processing. Each function receives the return value of the previous function as its parameter. the maximum number of functions is 18 + +## Returns + +(`PipeResult`): Processed result value, specifically the return value of the last function. If the initial value is a `Promise` or if there are async functions in the middle, the `pipe` function will return a `Promise` value. + +## Examples + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// Use pipe with async function +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// Use pipe with curried function +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/docs/zh_hans/reference/fp/core/pipe.md b/docs/zh_hans/reference/fp/core/pipe.md new file mode 100644 index 000000000..cf9ac63bc --- /dev/null +++ b/docs/zh_hans/reference/fp/core/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +这个函数只能从 `es-toolkit/fp` 导入,以支持函数式编程风格的代码编写。 + +从 `es-toolkit/fp` 导入函数后,可以使用管道语法或根据输入参数数量自动 curry 化的函数。 +::: + +通过管道处理值。在以声明性方式编写对值进行多步骤加工的代码时非常有用。 + +将要加工的初始值作为第一个参数传递给 `pipe` 函数,从第二个参数开始按顺序传递要加工的函数。这样,`pipe` 函数的第一个函数将接收初始值作为参数,其余函数将接收前一个函数返回的值,并按顺序执行,直到最后一个函数。 + +如果初始值是 `Promise` 值,或者在处理过程中存在异步函数,`pipe` 函数将异步处理值并返回 `Promise`。 + +## 签名 + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### 파라미터 + +- `initial` (`I`): 处理的初始值。 +- `fn1 ~ fn` (`F1 ~ F{n}`): 顺序处理值的函数。每个函数接收前一个函数的返回值作为参数执行。最多可以传递 18 个函数。 + +## 반환 값 + +(`PipeResult`): 处理后的结果值,即最后一个函数的返回值。如果初始值是 `Promise` 或者中间有异步函数,那么 `pipe` 函数将返回 `Promise` 值。 + +## 예시 + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// 可以与异步函数一起使用。 +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// 可以与 `curry` 化的函数一起使用。 +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/docs/zh_hans/reference/fp/function/pipe.md b/docs/zh_hans/reference/fp/function/pipe.md new file mode 100644 index 000000000..cf9ac63bc --- /dev/null +++ b/docs/zh_hans/reference/fp/function/pipe.md @@ -0,0 +1,453 @@ +# pipe + +::: info +这个函数只能从 `es-toolkit/fp` 导入,以支持函数式编程风格的代码编写。 + +从 `es-toolkit/fp` 导入函数后,可以使用管道语法或根据输入参数数量自动 curry 化的函数。 +::: + +通过管道处理值。在以声明性方式编写对值进行多步骤加工的代码时非常有用。 + +将要加工的初始值作为第一个参数传递给 `pipe` 函数,从第二个参数开始按顺序传递要加工的函数。这样,`pipe` 函数的第一个函数将接收初始值作为参数,其余函数将接收前一个函数返回的值,并按顺序执行,直到最后一个函数。 + +如果初始值是 `Promise` 值,或者在处理过程中存在异步函数,`pipe` 函数将异步处理值并返回 `Promise`。 + +## 签名 + +```typescript +export function pipe>(initial: I, fn1: F1): PipeResult<[I, F1]>; +export function pipe, F2 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2 +): PipeResult<[I, F1, F2]>; +export function pipe, F2 extends NextFunction, F3 extends NextFunction>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3 +): PipeResult<[I, F1, F2, F3]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4): PipeResult<[I, F1, F2, F3, F4]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5): PipeResult<[I, F1, F2, F3, F4, F5]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, +>(initial: I, fn1: F1, fn2: F2, fn3: F3, fn4: F4, fn5: F5, fn6: F6): PipeResult<[I, F1, F2, F3, F4, F5, F6]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17]>; +export function pipe< + I, + F1 extends NextFuntion, + F2 extends NextFunction, + F3 extends NextFunction, + F4 extends NextFunction, + F5 extends NextFunction, + F6 extends NextFunction, + F7 extends NextFunction, + F8 extends NextFunction, + F9 extends NextFunction, + F10 extends NextFunction, + F11 extends NextFunction, + F12 extends NextFunction, + F13 extends NextFunction, + F14 extends NextFunction, + F15 extends NextFunction, + F16 extends NextFunction, + F17 extends NextFunction, + F18 extends NextFunction, +>( + initial: I, + fn1: F1, + fn2: F2, + fn3: F3, + fn4: F4, + fn5: F5, + fn6: F6, + fn7: F7, + fn8: F8, + fn9: F9, + fn10: F10, + fn11: F11, + fn12: F12, + fn13: F13, + fn14: F14, + fn15: F15, + fn16: F16, + fn17: F17, + fn18: F18 +): PipeResult<[I, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18]>; +``` + +### 파라미터 + +- `initial` (`I`): 处理的初始值。 +- `fn1 ~ fn` (`F1 ~ F{n}`): 顺序处理值的函数。每个函数接收前一个函数的返回值作为参数执行。最多可以传递 18 个函数。 + +## 반환 값 + +(`PipeResult`): 处理后的结果值,即最后一个函数的返回值。如果初始值是 `Promise` 或者中间有异步函数,那么 `pipe` 函数将返回 `Promise` 值。 + +## 예시 + +```typescript +function toString(value: unknown) { + return `string:${value}`; +} + +function toStringAsync(value: unknown) { + return Promise.resolve(`string:${value}`); +} + +function length(value: string) { + return value.length; +} + +const result = pipe(1, toString, length); +console.log(result); // 8 + +// 可以与异步函数一起使用。 +const asyncResult = await pipe(1, toStringAsync, length); +console.log(asyncResult); // 8 + +// 可以与 `curry` 化的函数一起使用。 +const mapKeyResult = await pipe( + { a: 1, b: 2 }, + mapKeys((value, key) => key + value) +); +console.log(mapKeyResult); // { a1: 1, b2: 2 } +``` diff --git a/jsr.json b/jsr.json index 6d40c3537..e1998e662 100644 --- a/jsr.json +++ b/jsr.json @@ -3,7 +3,8 @@ "version": "1.31.0", "exports": { ".": "./src/index.ts", - "./compat": "./src/compat/index.ts" + "./compat": "./src/compat/index.ts", + "./fp": "./src/fp/index.ts" }, "publish": { "include": ["./src/**/*.ts"] diff --git a/package.json b/package.json index c4ece7432..662e360f0 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ ".": "./src/index.ts", "./array": "./src/array/index.ts", "./compat": "./src/compat/index.ts", + "./fp": "./src/fp/index.ts", "./error": "./src/error/index.ts", "./function": "./src/function/index.ts", "./math": "./src/math/index.ts", @@ -149,6 +150,16 @@ "default": "./dist/util/index.js" } }, + "./fp": { + "import": { + "types": "./dist/fp/index.d.mts", + "default": "./dist/fp/index.mjs" + }, + "require": { + "types": "./dist/fp/index.d.ts", + "default": "./dist/fp/index.js" + } + }, "./package.json": "./package.json" } }, @@ -167,6 +178,7 @@ "@types/tar": "^6.1.13", "@vitest/coverage-istanbul": "^2.1.2", "@vue/compiler-sfc": "^3.5.10", + "ast-types": "^0.14.2", "broken-link-checker": "^0.7.8", "eslint": "^9.9.0", "eslint-config-prettier": "^9.1.0", @@ -203,6 +215,7 @@ "bench": "vitest bench", "lint": "eslint --config eslint.config.mjs", "format": "prettier --write .", - "transform": "jscodeshift -t ./.scripts/tests/transform-lodash-test.ts $0 && prettier --write $0" + "transform": "jscodeshift -t ./.scripts/tests/transform-lodash-test.ts $0 && prettier --write $0", + "fp": "sh ./.scripts/fp/create-fp-version.sh $0" } -} \ No newline at end of file +} diff --git a/src/fp/array/at.spec.ts b/src/fp/array/at.spec.ts new file mode 100644 index 000000000..47bc5fc9a --- /dev/null +++ b/src/fp/array/at.spec.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; +import { at } from './at'; + +describe('at', () => { + it('(non-curried) should return the elements corresponding to the specified keys', () => { + expect(at(['a', 'b', 'c'], [0, 2])).toEqual(['a', 'c']); + expect(at(['a', 'b', 'c'], [2, 0])).toEqual(['c', 'a']); + }); + + it('(curried) should return the elements corresponding to the specified keys', () => { + expect(at([0, 2])(['a', 'b', 'c'])).toEqual(['a', 'c']); + expect(at([2, 0])(['a', 'b', 'c'])).toEqual(['c', 'a']); + }); + + it('(non-curried) should support negative indices', () => { + expect(at(['a', 'b', 'c'], [-1, -2])).toEqual(['c', 'b']); + expect(at(['a', 'b', 'c'], [-2, -1])).toEqual(['b', 'c']); + }); + + it('(curried) should support negative indices', () => { + expect(at([-1, -2])(['a', 'b', 'c'])).toEqual(['c', 'b']); + expect(at([-2, -1])(['a', 'b', 'c'])).toEqual(['b', 'c']); + }); + + it('(non-curried) should return `undefined` for nonexistent keys', () => { + expect(at(['a', 'b', 'c'], [2, 4, 0])).toEqual(['c', undefined, 'a']); + }); + + it('(curried) should return `undefined` for nonexistent keys', () => { + expect(at([2, 4, 0])(['a', 'b', 'c'])).toEqual(['c', undefined, 'a']); + }); + + it('(non-curried) should return an empty array when no keys are given', () => { + expect(at(['a', 'b', 'c'], [])).toEqual([]); + }); + + it('(curried) should return an empty array when no keys are given', () => { + expect(at([])(['a', 'b', 'c'])).toEqual([]); + }); +}); diff --git a/src/fp/array/at.ts b/src/fp/array/at.ts new file mode 100644 index 000000000..73d4facc4 --- /dev/null +++ b/src/fp/array/at.ts @@ -0,0 +1,43 @@ +import { at as atToolkit } from '../../array/at'; + +/** + * Retrieves elements from an array at the specified indices. + * + * This function supports negative indices, which count from the end of the array. + * + * @template T + * @param {readonly T[]} arr - The array to retrieve elements from. + * @param {number[]} indices - An array of indices specifying the positions of elements to retrieve. + * @returns {Array} A new array containing the elements at the specified indices. + * + * @example + * const numbers = [10, 20, 30, 40, 50]; + * const result = at(numbers, [1, 3, 4]); + * console.log(result); // [20, 40, 50] + */ +export function at(arr: readonly T[], indices: number[]): Array; + +/** + * Retrieves elements from an array at the specified indices. + * + * This function supports negative indices, which count from the end of the array. + * + * @template T + * @param {number[]} indices - An array of indices specifying the positions of elements to retrieve. + * @returns {(arr: readonly T[]) => Array} A function that receive the array to retrieve elements from as argument and returns a new array containing the elements at the specified indices. + * + * @example + * const numbers = [10, 20, 30, 40, 50]; + * const result = at([1, 3, 4])(numbers); + * console.log(result); // [20, 40, 50] + */ +export function at(indices: number[]): (arr: readonly T[]) => Array; + +export function at(arrOrIndices: readonly T[] | number[], indices?: number[]) { + if (indices == null) { + return (arr: readonly T[]) => at(arr, arrOrIndices as number[]); + } + + const arr = arrOrIndices as readonly T[]; + return atToolkit(arr, indices); +} diff --git a/src/fp/array/chunk.spec.ts b/src/fp/array/chunk.spec.ts new file mode 100644 index 000000000..48890a45e --- /dev/null +++ b/src/fp/array/chunk.spec.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest'; +import { chunk } from './chunk'; + +describe('chunk', () => { + it( + "(non-curried) should return an empty array when the input array is empty", + () => { + expect(chunk([], 3)).toEqual([]); + } + ); + + it("(curried) should return an empty array when the input array is empty", () => { + expect(chunk(3)([])).toEqual([]); + }); + + it( + "(non-curried) should throw if the size is not an integer of is less than 1", + () => { + expect(() => chunk([1, 2, 3], 0)).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + expect(() => chunk([1, 2, 3], -1)).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + expect(() => chunk([1, 2, 3], 0.5)).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + expect(() => chunk([1, 2, 3], Math.PI)).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + } + ); + + it("(curried) should throw if the size is not an integer of is less than 1", () => { + expect(() => chunk(0)([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + expect(() => chunk(-1)([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + expect(() => chunk(0.5)([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + expect(() => chunk(Math.PI)([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be an integer greater than zero.]` + ); + }); + + it( + "(non-curried) should evenly divide all elements into chunks of the specified size when the total length is a multiple of the size", + () => { + expect(chunk([1, 2, 3, 4, 5, 6], 3)).toEqual([ + [1, 2, 3], + [4, 5, 6], + ]); + } + ); + + it("(curried) should evenly divide all elements into chunks of the specified size when the total length is a multiple of the size", () => { + expect(chunk(3)([1, 2, 3, 4, 5, 6])).toEqual([ + [1, 2, 3], + [4, 5, 6], + ]); + }); + + it( + "(non-curried) should place the remaining elements in the last chunk when the total length is not a multiple of the size", + () => { + expect(chunk([1, 2, 3, 4], 6)).toEqual([[1, 2, 3, 4]]); + expect(chunk([1, 2, 3, 4, 5, 6, 7], 2)).toEqual([[1, 2], [3, 4], [5, 6], [7]]); + } + ); + + it("(curried) should place the remaining elements in the last chunk when the total length is not a multiple of the size", () => { + expect(chunk(6)([1, 2, 3, 4])).toEqual([[1, 2, 3, 4]]); + expect(chunk(2)([1, 2, 3, 4, 5, 6, 7])).toEqual([[1, 2], [3, 4], [5, 6], [7]]); + }); +}); diff --git a/src/fp/array/chunk.ts b/src/fp/array/chunk.ts new file mode 100644 index 000000000..63413334a --- /dev/null +++ b/src/fp/array/chunk.ts @@ -0,0 +1,59 @@ +import { chunk as chunkToolkit } from '../../array/chunk'; + +/** + * Splits an array into smaller arrays of a specified length. + * + * This function takes an input array and divides it into multiple smaller arrays, + * each of a specified length. If the input array cannot be evenly divided, + * the final sub-array will contain the remaining elements. + * + * @template T The type of elements in the array. + * @param {T[]} arr - The array to be chunked into smaller arrays. + * @param {number} size - The size of each smaller array. Must be a positive integer. + * @returns {T[][]} A two-dimensional array where each sub-array has a maximum length of `size`. + * @throws {Error} Throws an error if `size` is not a positive integer. + * + * @example + * // Splits an array of numbers into sub-arrays of length 2 + * chunk([1, 2, 3, 4, 5], 2); + * // Returns: [[1, 2], [3, 4], [5]] + * + * @example + * // Splits an array of strings into sub-arrays of length 3 + * chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3); + * // Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']] + */ +export function chunk(arr: readonly T[], size: number): T[][]; + +/** + * Splits an array into smaller arrays of a specified length. + * + * This function takes an input array and divides it into multiple smaller arrays, + * each of a specified length. If the input array cannot be evenly divided, + * the final sub-array will contain the remaining elements. + * + * @template T The type of elements in the array. + * @param {number} size - The size of each smaller array. Must be a positive integer. + * @returns {(arr: T[]) => T[][]} A function that receive the array to be chunked into smaller arrays as argument and returns a two-dimensional array where each sub-array has a maximum length of `size`. + * @throws {Error} Throws an error if `size` is not a positive integer. + * + * @example + * // Splits an array of numbers into sub-arrays of length 2 + * chunk(2, 3, 4, 5], 2)([1); + * // Returns: [[1, 2], [3, 4], [5]] + * + * @example + * // Splits an array of strings into sub-arrays of length 3 + * chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3); + * // Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']] + */ +export function chunk(size: number): (arr: readonly T[]) => T[][]; + +export function chunk(arrOrSize: readonly T[] | number, size?: number) { + if (size == null) { + return (arr: readonly T[]) => chunk(arr, arrOrSize as number); + } + + const arr = arrOrSize as readonly T[]; + return chunkToolkit(arr, size); +} diff --git a/src/fp/array/countBy.spec.ts b/src/fp/array/countBy.spec.ts new file mode 100644 index 000000000..c3e540c3b --- /dev/null +++ b/src/fp/array/countBy.spec.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; +import { countBy } from './countBy.ts'; + +describe('countBy', () => { + it('(non-curried) should count the occurrences of each item in an array', () => { + const arr = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]; + const result = countBy(arr, String); + + expect(result).toEqual({ + '1': 2, + '2': 2, + '3': 2, + '4': 2, + '5': 2, + }); + }); + + it('(curried) should count the occurrences of each item in an array', () => { + const arr = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]; + const result = countBy(String)(arr); + + expect(result).toEqual({ + '1': 2, + '2': 2, + '3': 2, + '4': 2, + '5': 2, + }); + }); + + it('(non-curried) should count the occurrences of each item in an array that applied transformer', () => { + const arr = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]; + const result = countBy(arr, item => (item % 2 === 0 ? 'even' : 'odd')); + + expect(result).toEqual({ + odd: 6, + even: 4, + }); + }); + + it('(curried) should count the occurrences of each item in an array that applied transformer', () => { + const arr = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]; + const result = countBy(item => (item % 2 === 0 ? 'even' : 'odd'))(arr); + + expect(result).toEqual({ + odd: 6, + even: 4, + }); + }); +}); diff --git a/src/fp/array/countBy.ts b/src/fp/array/countBy.ts new file mode 100644 index 000000000..9e430a8ab --- /dev/null +++ b/src/fp/array/countBy.ts @@ -0,0 +1,72 @@ +import { countBy as countByToolkit } from '../../array/countBy'; + +/** + * Count the occurrences of each item in an array + * based on a transformation function. + * + * This function takes an array and a transformation function + * that converts each item in the array to a key. It then + * counts the occurrences of each transformed item and returns + * an object with the transformed items as keys and the counts + * as values. + * + * @template T - The type of the items in the input array. + * @template K - The type of keys. + * @param {T[]} arr - The input array to count occurrences. + * @param {(item: T) => K} mapper - The transformation function that maps each item to a key. + * @returns {Record} An object containing the transformed items as keys and the + * counts as values. + * + * @example + * const array = ['a', 'b', 'c', 'a', 'b', 'a']; + * const result = countBy(array, x => x); + * // result will be { a: 3, b: 2, c: 1 } + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = countBy(array, item => item % 2 === 0 ? 'even' : 'odd'); + * // result will be { odd: 3, even: 2 } + */ +export function countBy(arr: readonly T[], mapper: (item: T) => K): Record; + +/** + * Count the occurrences of each item in an array + * based on a transformation function. + * + * This function takes an array and a transformation function + * that converts each item in the array to a key. It then + * counts the occurrences of each transformed item and returns + * an object with the transformed items as keys and the counts + * as values. + * + * @template T - The type of the items in the input array. + * @template K - The type of keys. + * @param {(item: T) => K} mapper - The transformation function that maps each item to a key. + * @returns {(arr: T[]) => Record} A function that receive the input array to count occurrences as argument and returns an object containing the transformed items as keys and the. + * counts as values. + * + * @example + * const array = ['a', 'b', 'c', 'a', 'b', 'a']; + * const result = countBy(x => x)(array); + * // result will be { a: 3, b: 2, c: 1 } + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = countBy(array, item => item % 2 === 0 ? 'even' : 'odd'); + * // result will be { odd: 3, even: 2 } + */ +export function countBy( + mapper: (item: T) => K +): (arr: readonly T[]) => Record; + +export function countBy( + arrOrMapper: readonly T[] | ((item: T) => K), + mapper?: (item: T) => K +) { + if (mapper == null) { + return (arr: readonly T[]) => countBy(arr, arrOrMapper as (item: T) => K); + } + + const arr = arrOrMapper as readonly T[]; + return countByToolkit(arr, mapper); +} diff --git a/src/fp/array/difference.spec.ts b/src/fp/array/difference.spec.ts new file mode 100644 index 000000000..44b25d6f6 --- /dev/null +++ b/src/fp/array/difference.spec.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { difference } from './difference'; + +describe('difference', () => { + it("(non-curried) should the difference of two arrays", () => { + expect(difference([1, 2, 3], [1])).toEqual([2, 3]); + expect(difference([], [1, 2, 3])).toEqual([]); + expect(difference([1, 2, 3, 4], [2, 4])).toEqual([1, 3]); + }); + + it("(curried) should the difference of two arrays", () => { + expect(difference([1])([1, 2, 3])).toEqual([2, 3]); + expect(difference([1, 2, 3])([])).toEqual([]); + expect(difference([2, 4])([1, 2, 3, 4])).toEqual([1, 3]); + }); +}); diff --git a/src/fp/array/difference.ts b/src/fp/array/difference.ts new file mode 100644 index 000000000..6ee56a022 --- /dev/null +++ b/src/fp/array/difference.ts @@ -0,0 +1,56 @@ +import { difference as differenceToolkit } from '../../array/difference'; + +/** + * Computes the difference between two arrays. + * + * This function takes two arrays and returns a new array containing the elements + * that are present in the first array but not in the second array. It effectively + * filters out any elements from the first array that also appear in the second array. + * + * @template T + * @param {T[]} firstArr - The array from which to derive the difference. This is the primary array + * from which elements will be compared and filtered. + * @param {T[]} secondArr - The array containing elements to be excluded from the first array. + * Each element in this array will be checked against the first array, and if a match is found, + * that element will be excluded from the result. + * @returns {T[]} A new array containing the elements that are present in the first array but not + * in the second array. + * + * @example + * const array1 = [1, 2, 3, 4, 5]; + * const array2 = [2, 4]; + * const result = difference(array1, array2); + * // result will be [1, 3, 5] since 2 and 4 are in both arrays and are excluded from the result. + */ +export function difference(firstArr: readonly T[], secondArr: readonly T[]): T[]; + +/** + * Computes the difference between two arrays. + * + * This function takes two arrays and returns a new array containing the elements + * that are present in the first array but not in the second array. It effectively + * filters out any elements from the first array that also appear in the second array. + * + * @template T + * @param {T[]} secondArr - The array containing elements to be excluded from the first array. + * Each element in this array will be checked against the first array, and if a match is found, + * that element will be excluded from the result. + * @returns {(firstArr: T[]) => T[]} A function that receive the array from which to derive the difference. This is the primary array as argument and returns a new array containing the elements that are present in the first array but not. + * in the second array. + * + * @example + * const array1 = [1, 2, 3, 4, 5]; + * const array2 = [2, 4]; + * const result = difference(array2)(array1); + * // result will be [1, 3, 5] since 2 and 4 are in both arrays and are excluded from the result. + */ +export function difference(secondArr: readonly T[]): (firstArr: readonly T[]) => T[]; + +export function difference(firstArrOrSecondArr: readonly T[] | readonly T[], secondArr?: readonly T[]) { + if (secondArr == null) { + return (firstArr: readonly T[]) => difference(firstArr, firstArrOrSecondArr as readonly T[]); + } + + const firstArr = firstArrOrSecondArr as readonly T[]; + return differenceToolkit(firstArr, secondArr); +} diff --git a/src/fp/array/differenceBy.spec.ts b/src/fp/array/differenceBy.spec.ts new file mode 100644 index 000000000..9fc30be3c --- /dev/null +++ b/src/fp/array/differenceBy.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; +import { differenceBy } from './differenceBy'; + +describe('differenceBy', () => { + it('should compute the difference using the mapper function', () => { + const arr = [2.1, 1.2, 3.3]; + expect(differenceBy(arr, [2.1, 1.2], Math.floor)).toEqual([3.3]); + expect(differenceBy([2.1, 1.2], Math.floor)(arr)).toEqual([3.3]); + }); + + it('should work with object arrays', () => { + const arr = [{ x: 1 }, { x: 2 }, { x: 3 }]; + expect(differenceBy(arr, [{ x: 1 }, { x: 2 }], obj => obj.x)).toEqual([{ x: 3 }]); + expect(differenceBy([{ x: 1 }, { x: 2 }], obj => obj.x)(arr)).toEqual([{ x: 3 }]); + }); + + it('should not modify the original array', () => { + const arr = [2.1, 1.2, 3.3]; + const arr2 = [2.1, 1.2, 3.3]; + + differenceBy(arr, [2.1], Math.floor); + differenceBy([2.1], Math.floor)(arr2); + + expect(arr).toEqual([2.1, 1.2, 3.3]); + expect(arr2).toEqual([2.1, 1.2, 3.3]); + }); +}); diff --git a/src/fp/array/differenceBy.ts b/src/fp/array/differenceBy.ts new file mode 100644 index 000000000..ff6c494ee --- /dev/null +++ b/src/fp/array/differenceBy.ts @@ -0,0 +1,54 @@ +import { differenceBy as differenceByToolkit } from '../../array/differenceBy'; + +/** + * Computes the difference between two arrays after mapping their elements through a provided function. + * + * @template T - The type of array. + * @template U - The type of the mapped value. + * @param {(value: T[number]) => U} mapper - The function to map the elements. + * @returns {(arr: T) => T} A function that takes an array and returns a new array with the difference. + * + * @example + * const numbers = [2.1, 1.2, 3.3]; + * const diffByFloor = differenceBy(Math.floor); + * const result = diffByFloor([2.1, 1.2]); + * // result will be [3.3] + */ +export function differenceBy( + values: T, + mapper: (value: T[number]) => U +): (arr: T) => T; +/** + * Computes the difference between two arrays after mapping their elements through a provided function. + * + * @template T - The type of array. + * @template U - The type of the mapped value. + * @param {T} arr - The array to inspect. + * @param {T} values - The values to exclude. + * @param {(value: T[number]) => U} mapper - The function to map the elements. + * @returns {T} A new array with the difference. + * + * @example + * const numbers = [2.1, 1.2, 3.3]; + * const result = differenceBy(numbers, [2.1, 1.2], Math.floor); + * // result will be [3.3] + */ +export function differenceBy( + arr: T, + values: T, + mapper: (value: T[number]) => U +): T; + +export function differenceBy( + arrOrValues: T, + valuesOrMapper: T | ((value: T[number]) => U), + mapper?: (value: T[number]) => U +) { + if (mapper === undefined && typeof valuesOrMapper === 'function') { + return (arr: T) => differenceBy(arr, arrOrValues, valuesOrMapper); + } + + const arr = arrOrValues as T; + const values = valuesOrMapper as T; + return differenceByToolkit(arr, values, mapper!) as T; +} diff --git a/src/fp/array/differenceWith.spec.ts b/src/fp/array/differenceWith.spec.ts new file mode 100644 index 000000000..5bcb1f16e --- /dev/null +++ b/src/fp/array/differenceWith.spec.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { differenceWith } from './differenceWith'; + +describe('differenceWith', () => { + it('should compute the difference using the comparator function', () => { + const arr = [{ x: 1 }, { x: 2 }, { x: 3 }]; + const comparator = (a: { x: number }, b: { x: number }) => a.x === b.x; + + expect(differenceWith(arr, [{ x: 1 }, { x: 2 }], comparator)).toEqual([{ x: 3 }]); + expect(differenceWith([{ x: 1 }, { x: 2 }], comparator)(arr)).toEqual([{ x: 3 }]); + }); + + it('should work with number arrays', () => { + const arr = [1.1, 2.2, 3.3]; + const comparator = (a: number, b: number) => Math.floor(a) === Math.floor(b); + + expect(differenceWith(arr, [1.9, 2.8], comparator)).toEqual([3.3]); + expect(differenceWith([1.9, 2.8], comparator)(arr)).toEqual([3.3]); + }); + + it('should not modify the original array', () => { + const arr = [{ x: 1 }, { x: 2 }, { x: 3 }]; + const arr2 = [{ x: 1 }, { x: 2 }, { x: 3 }]; + const comparator = (a: { x: number }, b: { x: number }) => a.x === b.x; + + differenceWith(arr, [{ x: 1 }], comparator); + differenceWith([{ x: 1 }], comparator)(arr2); + + expect(arr).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); + expect(arr2).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); + }); +}); diff --git a/src/fp/array/differenceWith.ts b/src/fp/array/differenceWith.ts new file mode 100644 index 000000000..08468334f --- /dev/null +++ b/src/fp/array/differenceWith.ts @@ -0,0 +1,53 @@ +import { differenceWith as differenceWithToolkit } from '../../array/differenceWith'; + +/** + * Computes the difference between two arrays using a custom comparator function. + * + * @template T - The type of array. + * @param {T} values - The values to exclude. + * @param {(x: T[number], y: T[number]) => boolean} comparator - The function to compare elements. + * @returns {(arr: T) => T} A function that takes an array and returns a new array with the difference. + * + * @example + * const objects = [{ x: 1 }, { x: 2 }, { x: 3 }]; + * const diffByX = differenceWith([{ x: 1 }, { x: 2 }], (a, b) => a.x === b.x); + * const result = diffByX(objects); + * // result will be [{ x: 3 }] + */ +export function differenceWith( + values: T, + comparator: (x: T[number], y: T[number]) => boolean +): (arr: T) => T; +/** + * Computes the difference between two arrays using a custom comparator function. + * + * @template T - The type of array. + * @param {T} arr - The array to inspect. + * @param {T} values - The values to exclude. + * @param {(x: T[number], y: T[number]) => boolean} comparator - The function to compare elements. + * @returns {T} A new array with the difference. + * + * @example + * const objects = [{ x: 1 }, { x: 2 }, { x: 3 }]; + * const result = differenceWith(objects, [{ x: 1 }, { x: 2 }], (a, b) => a.x === b.x); + * // result will be [{ x: 3 }] + */ +export function differenceWith( + arr: T, + values: T, + comparator: (x: T[number], y: T[number]) => boolean +): T; + +export function differenceWith( + arrOrValues: T, + valuesOrComparator: T | ((x: T[number], y: T[number]) => boolean), + comparator?: (x: T[number], y: T[number]) => boolean +) { + if (comparator === undefined && typeof valuesOrComparator === 'function') { + return (arr: T) => differenceWith(arr, arrOrValues, valuesOrComparator); + } + + const arr = arrOrValues as T; + const values = valuesOrComparator as T; + return differenceWithToolkit(arr, values, comparator!) as T; +} diff --git a/src/fp/array/drop.spec.ts b/src/fp/array/drop.spec.ts new file mode 100644 index 000000000..f60525c7e --- /dev/null +++ b/src/fp/array/drop.spec.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; +import { drop } from './drop'; + +describe('drop', () => { + it( + "(non-curried) should drop `itemsCount` elements from an array from the beginning", + () => { + expect(drop([1.2, 2.3, 3.4], 1)).toEqual([2.3, 3.4]); + expect(drop(['a', 'b', 'c', 'd'], 2)).toEqual(['c', 'd']); + } + ); + + it("(curried) should drop `itemsCount` elements from an array from the beginning", () => { + expect(drop(1)([1.2, 2.3, 3.4])).toEqual([2.3, 3.4]); + expect(drop(2)(['a', 'b', 'c', 'd'])).toEqual(['c', 'd']); + }); + + it("(non-curried) should return all elements if itemsCount < 1", () => { + expect(drop([1.2, 2.3, 3.4], 0)).toEqual([1.2, 2.3, 3.4]); + expect(drop([1.2, 2.3, 3.4], -1)).toEqual([1.2, 2.3, 3.4]); + }); + + it("(curried) should return all elements if itemsCount < 1", () => { + expect(drop(0)([1.2, 2.3, 3.4])).toEqual([1.2, 2.3, 3.4]); + expect(drop(-1)([1.2, 2.3, 3.4])).toEqual([1.2, 2.3, 3.4]); + }); + + it("(non-curried) should coerce itemsCount to an integer", () => { + expect(drop([1.2, 2.3, 3.4], 1.5)).toEqual([2.3, 3.4]); + }); + + it("(curried) should coerce itemsCount to an integer", () => { + expect(drop(1.5)([1.2, 2.3, 3.4])).toEqual([2.3, 3.4]); + }); + + it( + "(non-curried) should return empty array if itemsCount >= arr.length", + () => { + expect(drop([1.2, 2.3, 3.4], 4)).toEqual([]); + } + ); + + it("(curried) should return empty array if itemsCount >= arr.length", () => { + expect(drop(4)([1.2, 2.3, 3.4])).toEqual([]); + }); +}); diff --git a/src/fp/array/drop.ts b/src/fp/array/drop.ts new file mode 100644 index 000000000..915bb33c5 --- /dev/null +++ b/src/fp/array/drop.ts @@ -0,0 +1,45 @@ +import { drop as dropToolkit } from '../../array/drop'; + +/** + * Removes a specified number of elements from the beginning of an array and returns the rest. + * + * This function takes an array and a number, and returns a new array with the specified number + * of elements removed from the start. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array from which to drop elements. + * @param {number} itemsCount - The number of elements to drop from the beginning of the array. + * @returns {T[]} A new array with the specified number of elements removed from the start. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = drop(array, 2); + * // result will be [3, 4, 5] since the first two elements are dropped. + */ +export function drop(arr: readonly T[], itemsCount: number): T[]; + +/** + * Removes a specified number of elements from the beginning of an array and returns the rest. + * + * This function takes an array and a number, and returns a new array with the specified number + * of elements removed from the start. + * + * @template T - The type of elements in the array. + * @param {number} itemsCount - The number of elements to drop from the beginning of the array. + * @returns {(arr: T[]) => T[]} A function that receive the array from which to drop elements as argument and returns a new array with the specified number of elements removed from the start. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = drop(2)(array); + * // result will be [3, 4, 5] since the first two elements are dropped. + */ +export function drop(itemsCount: number): (arr: readonly T[]) => T[]; + +export function drop(arrOrItemsCount: readonly T[] | number, itemsCount?: number) { + if (itemsCount == null) { + return (arr: readonly T[]) => drop(arr, arrOrItemsCount as number); + } + + const arr = arrOrItemsCount as readonly T[]; + return dropToolkit(arr, itemsCount); +} diff --git a/src/fp/array/dropRight.spec.ts b/src/fp/array/dropRight.spec.ts new file mode 100644 index 000000000..e2e8b3c96 --- /dev/null +++ b/src/fp/array/dropRight.spec.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; +import { dropRight } from './dropRight'; + +describe('dropRight', () => { + it( + "(non-curried) should drop `itemsCount` elements from an array from the end", + () => { + expect(dropRight([1.2, 2.3, 3.4], 1)).toEqual([1.2, 2.3]); + expect(dropRight(['a', 'b', 'c', 'd'], 2)).toEqual(['a', 'b']); + } + ); + + it("(curried) should drop `itemsCount` elements from an array from the end", () => { + expect(dropRight(1)([1.2, 2.3, 3.4])).toEqual([1.2, 2.3]); + expect(dropRight(2)(['a', 'b', 'c', 'd'])).toEqual(['a', 'b']); + }); + + it("(non-curried) should return all elements if itemsCount < 1", () => { + expect(dropRight([1.2, 2.3, 3.4], 0)).toEqual([1.2, 2.3, 3.4]); + expect(dropRight([1.2, 2.3, 3.4], -1)).toEqual([1.2, 2.3, 3.4]); + }); + + it("(curried) should return all elements if itemsCount < 1", () => { + expect(dropRight(0)([1.2, 2.3, 3.4])).toEqual([1.2, 2.3, 3.4]); + expect(dropRight(-1)([1.2, 2.3, 3.4])).toEqual([1.2, 2.3, 3.4]); + }); + + it("(non-curried) should coerce itemsCount to an integer", () => { + expect(dropRight([1.2, 2.3, 3.4], 1.5)).toEqual([1.2, 2.3]); + }); + + it("(curried) should coerce itemsCount to an integer", () => { + expect(dropRight(1.5)([1.2, 2.3, 3.4])).toEqual([1.2, 2.3]); + }); + + it( + "(non-curried) should return empty array if itemsCount >= arr.length", + () => { + expect(dropRight([1.2, 2.3, 3.4], 4)).toEqual([]); + } + ); + + it("(curried) should return empty array if itemsCount >= arr.length", () => { + expect(dropRight(4)([1.2, 2.3, 3.4])).toEqual([]); + }); +}); diff --git a/src/fp/array/dropRight.ts b/src/fp/array/dropRight.ts new file mode 100644 index 000000000..736bbd8be --- /dev/null +++ b/src/fp/array/dropRight.ts @@ -0,0 +1,45 @@ +import { dropRight as dropRightToolkit } from '../../array/dropRight'; + +/** + * Removes a specified number of elements from the end of an array and returns the rest. + * + * This function takes an array and a number, and returns a new array with the specified number + * of elements removed from the end. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array from which to drop elements. + * @param {number} itemsCount - The number of elements to drop from the end of the array. + * @returns {T[]} A new array with the specified number of elements removed from the end. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = dropRight(array, 2); + * // result will be [1, 2, 3] since the last two elements are dropped. + */ +export function dropRight(arr: readonly T[], itemsCount: number): T[]; + +/** + * Removes a specified number of elements from the end of an array and returns the rest. + * + * This function takes an array and a number, and returns a new array with the specified number + * of elements removed from the end. + * + * @template T - The type of elements in the array. + * @param {number} itemsCount - The number of elements to drop from the end of the array. + * @returns {(arr: T[]) => T[]} A function that receive the array from which to drop elements as argument and returns a new array with the specified number of elements removed from the end. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = dropRight(2)(array); + * // result will be [1, 2, 3] since the last two elements are dropped. + */ +export function dropRight(itemsCount: number): (arr: readonly T[]) => T[]; + +export function dropRight(arrOrItemsCount: readonly T[] | number, itemsCount?: number) { + if (itemsCount == null) { + return (arr: readonly T[]) => dropRight(arr, arrOrItemsCount as number); + } + + const arr = arrOrItemsCount as readonly T[]; + return dropRightToolkit(arr, itemsCount); +} diff --git a/src/fp/array/dropRightWhile.spec.ts b/src/fp/array/dropRightWhile.spec.ts new file mode 100644 index 000000000..0317517c6 --- /dev/null +++ b/src/fp/array/dropRightWhile.spec.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; +import { dropRightWhile } from './dropRightWhile'; + +describe('dropRightWhile', () => { + it( + "(non-curried) should drop elements from an array until `canContinueDropping` returns false, from the end", + () => { + expect(dropRightWhile([1.2, 2.3, 3.4], x => x < 2)).toEqual([1.2, 2.3, 3.4]); + + const items = [ + { id: 1, enabled: false }, + { id: 2, enabled: true }, + { id: 3, enabled: false }, + ]; + + expect(dropRightWhile(items, x => !x.enabled)).toEqual([ + { + id: 1, + enabled: false, + }, + { + id: 2, + enabled: true, + }, + ]); + + expect(dropRightWhile([1, 2, 3], x => x < 4)).toEqual([]); + } + ); + + it("(curried) should drop elements from an array until `canContinueDropping` returns false, from the end", () => { + expect(dropRightWhile(x => x < 2)([1.2, 2.3, 3.4])).toEqual([1.2, 2.3, 3.4]); + + const items = [ + { id: 1, enabled: false }, + { id: 2, enabled: true }, + { id: 3, enabled: false }, + ]; + + expect(dropRightWhile<{ id: number; enabled: boolean }>(x => !x.enabled)(items)).toEqual([ + { + id: 1, + enabled: false, + }, + { + id: 2, + enabled: true, + }, + ]); + + expect(dropRightWhile(x => x < 4)([1, 2, 3])).toEqual([]); + }); +}); diff --git a/src/fp/array/dropRightWhile.ts b/src/fp/array/dropRightWhile.ts new file mode 100644 index 000000000..0101066e8 --- /dev/null +++ b/src/fp/array/dropRightWhile.ts @@ -0,0 +1,58 @@ +import { dropRightWhile as dropRightWhileToolkit } from '../../array/dropRightWhile'; + +/** + * Removes elements from the end of an array until the predicate returns false. + * + * This function iterates over an array from the end and drops elements until the provided + * predicate function returns false. It then returns a new array with the remaining elements. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array from which to drop elements. + * @param {(item: T, index: number, arr: T[]) => boolean} canContinueDropping - A predicate function that determines + * whether to continue dropping elements. The function is called with each element from the end, + * and dropping continues as long as it returns true. + * @returns {T[]} A new array with the elements remaining after the predicate returns false. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = dropRightWhile(array, x => x > 3); + * // result will be [1, 2, 3] since elements greater than 3 are dropped from the end. + */ +export function dropRightWhile( + arr: readonly T[], + canContinueDropping: (item: T, index: number, arr: readonly T[]) => boolean +): T[]; + +/** + * Removes elements from the end of an array until the predicate returns false. + * + * This function iterates over an array from the end and drops elements until the provided + * predicate function returns false. It then returns a new array with the remaining elements. + * + * @template T - The type of elements in the array. + * @param {(item: T, index: number, arr: T[]) => boolean} canContinueDropping - A predicate function that determines + * whether to continue dropping elements. The function is called with each element from the end, + * and dropping continues as long as it returns true. + * @returns {(arr: T[]) => T[]} A function that receive the array from which to drop elements as argument and returns a new array with the elements remaining after the predicate returns false. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = dropRightWhile(x => x > 3)(array); + * // result will be [1, 2, 3] since elements greater than 3 are dropped from the end. + */ +export function dropRightWhile( + canContinueDropping: (item: T, index: number, arr: readonly T[]) => boolean +): (arr: readonly T[]) => T[]; + +export function dropRightWhile( + arrOrCanContinueDropping: readonly T[] | ((item: T, index: number, arr: readonly T[]) => boolean), + canContinueDropping?: (item: T, index: number, arr: readonly T[]) => boolean +) { + if (canContinueDropping == null) { + return (arr: readonly T[]) => + dropRightWhile(arr, arrOrCanContinueDropping as (item: T, index: number, arr: readonly T[]) => boolean); + } + + const arr = arrOrCanContinueDropping as readonly T[]; + return dropRightWhileToolkit(arr, canContinueDropping); +} diff --git a/src/fp/array/dropWhile.spec.ts b/src/fp/array/dropWhile.spec.ts new file mode 100644 index 000000000..37a221869 --- /dev/null +++ b/src/fp/array/dropWhile.spec.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest'; +import { dropWhile } from './dropWhile'; + +describe('dropWhile', () => { + it( + "(non-curried) should drop elements from an array until `canContinueDropping` returns false, from the beginning", + () => { + expect(dropWhile([1.2, 2.3, 3.4], x => x < 2)).toEqual([2.3, 3.4]); + + const items = [ + { id: 1, enabled: false }, + { id: 2, enabled: true }, + { id: 3, enabled: false }, + ]; + + expect(dropWhile(items, x => !x.enabled)).toEqual([ + { + id: 2, + enabled: true, + }, + { id: 3, enabled: false }, + ]); + + expect(dropWhile([1, 2, 3], x => x < 4)).toEqual([]); + } + ); + + it("(curried) should drop elements from an array until `canContinueDropping` returns false, from the beginning", () => { + expect(dropWhile(x => x < 2)([1.2, 2.3, 3.4])).toEqual([2.3, 3.4]); + + const items = [ + { id: 1, enabled: false }, + { id: 2, enabled: true }, + { id: 3, enabled: false }, + ]; + + expect(dropWhile<{ id: number; enabled: boolean }>(x => !x.enabled)(items)).toEqual([ + { + id: 2, + enabled: true, + }, + { id: 3, enabled: false }, + ]); + + expect(dropWhile(x => x < 4)([1, 2, 3])).toEqual([]); + }); +}); diff --git a/src/fp/array/dropWhile.ts b/src/fp/array/dropWhile.ts new file mode 100644 index 000000000..b91f283ac --- /dev/null +++ b/src/fp/array/dropWhile.ts @@ -0,0 +1,58 @@ +import { dropWhile as dropWhileToolkit } from '../../array/dropWhile'; + +/** + * Removes elements from the beginning of an array until the predicate returns false. + * + * This function iterates over an array and drops elements from the start until the provided + * predicate function returns false. It then returns a new array with the remaining elements. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array from which to drop elements. + * @param {(item: T, index: number, arr: T[]) => boolean} canContinueDropping - A predicate function that determines + * whether to continue dropping elements. The function is called with each element, and dropping + * continues as long as it returns true. + * @returns {T[]} A new array with the elements remaining after the predicate returns false. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = dropWhile(array, x => x < 3); + * // result will be [3, 4, 5] since elements less than 3 are dropped. + */ +export function dropWhile( + arr: readonly T[], + canContinueDropping: (item: T, index: number, arr: readonly T[]) => boolean +): T[]; + +/** + * Removes elements from the beginning of an array until the predicate returns false. + * + * This function iterates over an array and drops elements from the start until the provided + * predicate function returns false. It then returns a new array with the remaining elements. + * + * @template T - The type of elements in the array. + * @param {(item: T, index: number, arr: T[]) => boolean} canContinueDropping - A predicate function that determines + * whether to continue dropping elements. The function is called with each element, and dropping + * continues as long as it returns true. + * @returns {(arr: T[]) => T[]} A function that receive the array from which to drop elements as argument and returns a new array with the elements remaining after the predicate returns false. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const result = dropWhile(x => x < 3)(array); + * // result will be [3, 4, 5] since elements less than 3 are dropped. + */ +export function dropWhile( + canContinueDropping: (item: T, index: number, arr: readonly T[]) => boolean +): (arr: readonly T[]) => T[]; + +export function dropWhile( + arrOrCanContinueDropping: readonly T[] | ((item: T, index: number, arr: readonly T[]) => boolean), + canContinueDropping?: (item: T, index: number, arr: readonly T[]) => boolean +) { + if (canContinueDropping == null) { + return (arr: readonly T[]) => + dropWhile(arr, arrOrCanContinueDropping as (item: T, index: number, arr: readonly T[]) => boolean); + } + + const arr = arrOrCanContinueDropping as readonly T[]; + return dropWhileToolkit(arr, canContinueDropping); +} diff --git a/src/fp/array/every.spec.ts b/src/fp/array/every.spec.ts new file mode 100644 index 000000000..7ad575b4f --- /dev/null +++ b/src/fp/array/every.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { every } from './every'; + +describe('every', () => { + it('should return true when all elements pass the test', () => { + expect(every([2, 4, 6, 8], value => value % 2 === 0)).toBe(true); + expect(every(value => value % 2 === 0)([2, 4, 6, 8])).toBe(true); + }); + + it('should return false when any element fails the test', () => { + expect(every([1, 2, 3, 4], value => value % 2 === 0)).toBe(false); + expect(every(value => value % 2 === 0)([1, 2, 3, 4])).toBe(false); + }); + + it('should handle empty arrays', () => { + expect(every([], () => false)).toBe(true); + expect(every(() => false)([])).toBe(true); + }); + + it('should work with different types', () => { + expect(every(['a', 'ab', 'abc'], str => str.length > 0)).toBe(true); + expect(every(str => str.length > 0)(['a', 'ab', 'abc'])).toBe(true); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + every(arr, value => value > 0); + every(value => value > 0)(arr2); + + expect(arr).toEqual([1, 2, 3, 4]); + expect(arr2).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/src/fp/array/every.ts b/src/fp/array/every.ts new file mode 100644 index 000000000..7102e5b07 --- /dev/null +++ b/src/fp/array/every.ts @@ -0,0 +1,47 @@ +/** + * Tests whether all elements in the array pass the test implemented by the provided function. + * + * @template T - The type of array. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {(arr: T) => boolean} A function that takes an array and returns whether all elements pass the test. + * + * @example + * const arr = [2, 4, 6, 8]; + * const isEven = every(value => value % 2 === 0); + * const result = isEven(arr); + * // result will be true + */ +export function every(predicate: (value: T[number]) => boolean): (arr: T) => boolean; +/** + * Tests whether all elements in the array pass the test implemented by the provided function. + * + * @template T - The type of array. + * @param {T} arr - The array to test. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {boolean} Whether all elements in the array pass the test. + * + * @example + * const arr = [2, 4, 6, 8]; + * const result = every(arr, value => value % 2 === 0); + * // result will be true + */ +export function every(arr: T, predicate: (value: T[number]) => boolean): boolean; + +export function every( + arrOrPredicate: T | ((value: T[number]) => boolean), + predicate?: (value: T[number]) => boolean +) { + if (predicate == null) { + return (arr: T) => every(arr, arrOrPredicate as (value: T[number]) => boolean); + } + + const arr = arrOrPredicate as T[]; + + for (const item of arr) { + if (!predicate(item)) { + return false; + } + } + + return true; +} diff --git a/src/fp/array/fill.spec.ts b/src/fp/array/fill.spec.ts new file mode 100644 index 000000000..acbe232ca --- /dev/null +++ b/src/fp/array/fill.spec.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from 'vitest'; +import { fill } from './fill'; + +describe('fill', () => { + it('(non-curried) should fill the entire array with the specified value', () => { + expect(fill([1, 2, 3], 'a')).toEqual(['a', 'a', 'a']); + expect(fill(Array(3), 2)).toEqual([2, 2, 2]); + }); + + it('(curried) should fill the entire array with the specified value', () => { + expect(fill('a')([1, 2, 3])).toEqual(['a', 'a', 'a']); + expect(fill(2)(Array(3))).toEqual([2, 2, 2]); + }); + + it('(non-curried) should fill part of an array from start to end index', () => { + expect(fill([4, 6, 8, 10], '*', 1, 3)).toEqual([4, '*', '*', 10]); + expect(fill([1, 2, 3, 4, 5], '*', 1, 4)).toEqual([1, '*', '*', '*', 5]); + }); + + it('(curried) should fill part of an array from start to end index', () => { + expect(fill('*', 1, 3)([4, 6, 8, 10])).toEqual([4, '*', '*', 10]); + expect(fill('*', 1, 4)([1, 2, 3, 4, 5])).toEqual([1, '*', '*', '*', 5]); + }); + + it('(non-curried) should fill from specified start position', () => { + expect(fill([1, 2, 3, 4, 5], '*', 2)).toEqual([1, 2, '*', '*', '*']); + }); + + it('(curried) should fill from specified start position', () => { + expect(fill('*', 2)([1, 2, 3, 4, 5])).toEqual([1, 2, '*', '*', '*']); + }); + + it('(non-curried) should fill with negative start position', () => { + expect(fill([1, 2, 3, 4, 5], '*', -3)).toEqual([1, 2, '*', '*', '*']); + }); + + it('(curried) should fill with negative start position', () => { + expect(fill('*', -3)([1, 2, 3, 4, 5])).toEqual([1, 2, '*', '*', '*']); + }); + + it('(non-curried) should fill with positive start and negative end positions', () => { + expect(fill([1, 2, 3, 4, 5], '*', 1, -1)).toEqual([1, '*', '*', '*', 5]); + }); + + it('(curried) should fill with positive start and negative end positions', () => { + expect(fill('*', 1, -1)([1, 2, 3, 4, 5])).toEqual([1, '*', '*', '*', 5]); + }); + + it('(non-curried) should fill with both negative start and end positions', () => { + expect(fill([1, 2, 3, 4, 5], '*', -4, -1)).toEqual([1, '*', '*', '*', 5]); + }); + + it('(curried) should fill with both negative start and end positions', () => { + expect(fill('*', -4, -1)([1, 2, 3, 4, 5])).toEqual([1, '*', '*', '*', 5]); + }); + + it('(non-curried) should not fill if start is greater than end', () => { + expect(fill([1, 2, 3, 4, 5], '*', 3, 2)).toEqual([1, 2, 3, 4, 5]); + }); + + it('(curried) should not fill if start is greater than end', () => { + expect(fill('*', 3, 2)([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]); + }); +}); diff --git a/src/fp/array/fill.ts b/src/fp/array/fill.ts new file mode 100644 index 000000000..22ab18061 --- /dev/null +++ b/src/fp/array/fill.ts @@ -0,0 +1,48 @@ +import { fill as fillToolkit } from '../../array/fill'; + +/** + * Creates a function that fills elements of array with value from start up to end. + * + * @template T - The type of array elements. + * @param {T} value - The value to fill array with. + * @param {number} [start=0] - The start position. + * @param {number} [end=array.length] - The end position. + * @returns {(arr: T[]) => T[]} A function that takes an array and returns a new filled array. + * + * @example + * const fillWithA = fill('a', 1, 3); + * const result = fillWithA(['x', 'x', 'x', 'x']); + * // result will be ['x', 'a', 'a', 'x'] + */ +export function fill(value: U, start?: number, end?: number): (arr: T[]) => Array; +/** + * Fills elements of array with value from start up to end. + * + * @template T - The type of array elements. + * @param {T[]} arr - The array to fill. + * @param {T} value - The value to fill array with. + * @param {number} [start=0] - The start position. + * @param {number} [end=array.length] - The end position. + * @returns {T[]} A new array with filled elements. + * + * @example + * const array = ['x', 'x', 'x', 'x']; + * const result = fill(array, 'a', 1, 3); + * // result will be ['x', 'a', 'a', 'x'] + */ +export function fill(arr: T[], value: U, start?: number, end?: number): Array; + +export function fill( + arrOrValue: T[] | T, + valueOrStart?: U | number, + startOrEnd?: number, + end?: number +) { + if (!Array.isArray(arrOrValue)) { + return (arr: T[]) => fill(arr, arrOrValue, valueOrStart as number, startOrEnd); + } + + const arr = arrOrValue; + const value = valueOrStart as T; + return fillToolkit(arr, value, startOrEnd ?? 0, end ?? arr.length); +} diff --git a/src/fp/array/filter.spec.ts b/src/fp/array/filter.spec.ts new file mode 100644 index 000000000..664870a54 --- /dev/null +++ b/src/fp/array/filter.spec.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { filter } from './filter'; + +describe('filter', () => { + const arr = [1, 2, 3, 4, 5]; + const isEven = (value: number) => value % 2 === 0; + + describe('when called with array and predicate', () => { + it('should filter array values by predicate', () => { + const result = filter(arr, isEven); + expect(result).toEqual([2, 4]); + }); + + it('should return empty array when no values match predicate', () => { + const result = filter(arr, value => value > 10); + expect(result).toEqual([]); + }); + + it('should return same array when all values match predicate', () => { + const result = filter(arr, value => value > 0); + expect(result).toEqual(arr); + }); + }); + + describe('when called with predicate only', () => { + it('should return a function that filters array values', () => { + const filterEven = filter(isEven); + const result = filterEven(arr); + expect(result).toEqual([2, 4]); + }); + + it('should maintain curried function behavior', () => { + const greaterThan = (min: number) => filter(value => value > min); + const greaterThan3 = greaterThan(3); + const result = greaterThan3(arr); + expect(result).toEqual([4, 5]); + }); + }); +}); diff --git a/src/fp/array/filter.ts b/src/fp/array/filter.ts new file mode 100644 index 000000000..85ecd4f0d --- /dev/null +++ b/src/fp/array/filter.ts @@ -0,0 +1,48 @@ +/** + * Filter array values by predicate function. + * + * @template T - The type of array. + * @param {(value: T[number]) => boolean} predicate - The function that tests each item. + * @returns {(arr: T) => T[number][]} A function that takes an array and returns filtered values. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const isEven = filter(value => value % 2 === 0); + * const result = isEven(arr); + * // result will be [2, 4] + */ +export function filter(predicate: (value: T[number]) => boolean): (arr: T) => T[number][]; +/** + * Filter array values by predicate function. + * + * @template T - The type of array. + * @param {T} arr - The array to be filtered. + * @param {(value: T[number]) => boolean} predicate - The function that tests each item. + * @returns {T[number][]} A new array with filtered values. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const result = filter(arr, value => value % 2 === 0); + * // result will be [2, 4] + */ +export function filter(arr: T, predicate: (value: T[number]) => boolean): T[number][]; + +export function filter( + arrOrPredicate: T | ((value: T[number]) => boolean), + predicate?: (value: T[number]) => boolean +) { + if (predicate == null) { + return (arr: T) => filter(arr, arrOrPredicate as (value: T[number]) => boolean); + } + + const arr = arrOrPredicate as T[]; + const result = []; + + for (const item of arr) { + if (predicate(item)) { + result.push(item); + } + } + + return result; +} diff --git a/src/fp/array/find.spec.ts b/src/fp/array/find.spec.ts new file mode 100644 index 000000000..8eef00782 --- /dev/null +++ b/src/fp/array/find.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { find } from './find'; + +describe('find', () => { + it('should return the first element that passes the test', () => { + expect(find([1, 2, 3, 4], value => value % 2 === 0)).toBe(2); + expect(find(value => value % 2 === 0)([1, 2, 3, 4])).toBe(2); + }); + + it('should return undefined when no element passes the test', () => { + expect(find([1, 3, 5, 7], value => value % 2 === 0)).toBeUndefined(); + expect(find(value => value % 2 === 0)([1, 3, 5, 7])).toBeUndefined(); + }); + + it('should handle empty arrays', () => { + expect(find([], () => true)).toBeUndefined(); + expect(find(() => true)([])).toBeUndefined(); + }); + + it('should work with different types', () => { + expect(find(['abc', 'def', 'ghi'], str => str.startsWith('d'))).toBe('def'); + expect(find(str => str.startsWith('d'))(['abc', 'def', 'ghi'])).toBe('def'); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + find(arr, value => value > 2); + find(value => value > 2)(arr2); + + expect(arr).toEqual([1, 2, 3, 4]); + expect(arr2).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/src/fp/array/find.ts b/src/fp/array/find.ts new file mode 100644 index 000000000..55a7e3692 --- /dev/null +++ b/src/fp/array/find.ts @@ -0,0 +1,47 @@ +/** + * Returns the first element in the array that satisfies the provided testing function. + * + * @template T - The type of array. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {(arr: T) => T[number] | undefined} A function that takes an array and returns the first element that satisfies the test, or undefined if no element is found. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const findFirstEven = find(value => value % 2 === 0); + * const result = findFirstEven(arr); + * // result will be 2 + */ +export function find(predicate: (value: T[number]) => boolean): (arr: T) => T[number] | undefined; +/** + * Returns the first element in the array that satisfies the provided testing function. + * + * @template T - The type of array. + * @param {T} arr - The array to search. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {T[number] | undefined} The first element that satisfies the test, or undefined if no element is found. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const result = find(arr, value => value % 2 === 0); + * // result will be 2 + */ +export function find(arr: T, predicate: (value: T[number]) => boolean): T[number] | undefined; + +export function find( + arrOrPredicate: T | ((value: T[number]) => boolean), + predicate?: (value: T[number]) => boolean +) { + if (predicate == null) { + return (arr: T) => find(arr, arrOrPredicate as (value: T[number]) => boolean); + } + + const arr = arrOrPredicate as T[]; + + for (const item of arr) { + if (predicate(item)) { + return item; + } + } + + return undefined; +} diff --git a/src/fp/array/findIndex.spec.ts b/src/fp/array/findIndex.spec.ts new file mode 100644 index 000000000..36232b308 --- /dev/null +++ b/src/fp/array/findIndex.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { findIndex } from './findIndex'; + +describe('findIndex', () => { + it('should return the index of first element that passes the test', () => { + expect(findIndex([1, 2, 3, 4], value => value % 2 === 0)).toBe(1); + expect(findIndex(value => value % 2 === 0)([1, 2, 3, 4])).toBe(1); + }); + + it('should return -1 when no element passes the test', () => { + expect(findIndex([1, 3, 5, 7], value => value % 2 === 0)).toBe(-1); + expect(findIndex(value => value % 2 === 0)([1, 3, 5, 7])).toBe(-1); + }); + + it('should handle empty arrays', () => { + expect(findIndex([], () => true)).toBe(-1); + expect(findIndex(() => true)([])).toBe(-1); + }); + + it('should work with different types', () => { + expect(findIndex(['abc', 'def', 'ghi'], str => str.startsWith('d'))).toBe(1); + expect(findIndex(str => str.startsWith('d'))(['abc', 'def', 'ghi'])).toBe(1); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + findIndex(arr, value => value > 2); + findIndex(value => value > 2)(arr2); + + expect(arr).toEqual([1, 2, 3, 4]); + expect(arr2).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/src/fp/array/findIndex.ts b/src/fp/array/findIndex.ts new file mode 100644 index 000000000..e36d41da5 --- /dev/null +++ b/src/fp/array/findIndex.ts @@ -0,0 +1,47 @@ +/** + * Returns the index of the first element in the array that satisfies the provided testing function. + * + * @template T - The type of array. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {(arr: T) => number} A function that takes an array and returns the index of the first element that satisfies the test, or -1 if no element is found. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const findFirstEvenIndex = findIndex(value => value % 2 === 0); + * const result = findFirstEvenIndex(arr); + * // result will be 1 + */ +export function findIndex(predicate: (value: T[number]) => boolean): (arr: T) => number; +/** + * Returns the index of the first element in the array that satisfies the provided testing function. + * + * @template T - The type of array. + * @param {T} arr - The array to search. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {number} The index of the first element that satisfies the test, or -1 if no element is found. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const result = findIndex(arr, value => value % 2 === 0); + * // result will be 1 + */ +export function findIndex(arr: T, predicate: (value: T[number]) => boolean): number; + +export function findIndex( + arrOrPredicate: T | ((value: T[number]) => boolean), + predicate?: (value: T[number]) => boolean +) { + if (predicate == null) { + return (arr: T) => findIndex(arr, arrOrPredicate as (value: T[number]) => boolean); + } + + const arr = arrOrPredicate as T[]; + + for (let i = 0; i < arr.length; i++) { + if (predicate(arr[i])) { + return i; + } + } + + return -1; +} diff --git a/src/fp/array/flatMap.spec.ts b/src/fp/array/flatMap.spec.ts new file mode 100644 index 000000000..3f9966a21 --- /dev/null +++ b/src/fp/array/flatMap.spec.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest'; +import { flatMap } from './flatMap'; + +describe('flatMap', () => { + it('(non-curried) should map and flatten array values', () => { + expect(flatMap([1, 2, 3], x => [x, x * 2])).toEqual([1, 2, 2, 4, 3, 6]); + expect(flatMap(['a', 'b'], x => [x, x])).toEqual(['a', 'a', 'b', 'b']); + }); + + it('(curried) should map and flatten array values', () => { + expect(flatMap(x => [x, x * 2])([1, 2, 3])).toEqual([1, 2, 2, 4, 3, 6]); + expect(flatMap(x => [x, x])(['a', 'b'])).toEqual(['a', 'a', 'b', 'b']); + }); + + it('(non-curried) should handle empty arrays', () => { + expect(flatMap([], x => [x, x])).toEqual([]); + }); + + it('(curried) should handle empty arrays', () => { + expect(flatMap(x => [x, x])([])).toEqual([]); + }); + + it('(non-curried) should handle empty result arrays', () => { + expect(flatMap([1, 2, 3], () => [])).toEqual([]); + }); + + it('(curried) should handle empty result arrays', () => { + expect(flatMap(() => [])([1, 2, 3])).toEqual([]); + }); + + it('(non-curried) should handle nested arrays', () => { + expect(flatMap([1, 2], x => [[x, x]])).toEqual([[1, 1], [2, 2]]); + }); + + it('(curried) should handle nested arrays', () => { + expect(flatMap(x => [[x, x]])([1, 2])).toEqual([[1, 1], [2, 2]]); + }); +}); \ No newline at end of file diff --git a/src/fp/array/flatMap.ts b/src/fp/array/flatMap.ts new file mode 100644 index 000000000..88be287e5 --- /dev/null +++ b/src/fp/array/flatMap.ts @@ -0,0 +1,44 @@ +import { flatMap as flatMapToolkit } from '../../array/flatMap'; + +/** + * Map each values of array by mapper function and flatten the result. + * + * @template T - The type of array. + * @template R - The type of mapped value. + * @param {(value: T[number]) => R[]} mapper - The function that map each items to array of new values. + * @returns {(arr: T) => R[]} A function that takes an array and returns flattened mapped values. + * + * @example + * const arr = [1, 2, 3]; + * const duplicate = flatMap(value => [value, value]); + * const result = duplicate(arr); + * // result will be [1, 1, 2, 2, 3, 3] + */ +export function flatMap(mapper: (value: T[number]) => R[]): (arr: T) => R[]; +/** + * Map each values of array by mapper function and flatten the result. + * + * @template T - The type of array. + * @template R - The type of mapped value. + * @param {T} arr - The array to be mapped. + * @param {(value: T[number]) => R[]} mapper - The function that map each items to array of new values. + * @returns {R[]} A new flattened array with mapped values. + * + * @example + * const arr = [1, 2, 3]; + * const result = flatMap(arr, value => [value, value * 2]); + * // result will be [1, 2, 2, 4, 3, 6] + */ +export function flatMap(arr: T, mapper: (value: T[number]) => R[]): R[]; + +export function flatMap( + arrOrMapper: T | ((value: T[number]) => R[]), + mapper?: (value: T[number]) => R[] +) { + if (mapper == null) { + return (arr: T) => flatMap(arr, arrOrMapper as (value: T[number]) => R[]); + } + + const arr = arrOrMapper as T[]; + return flatMapToolkit(arr, mapper as any); +} diff --git a/src/fp/array/flatMapDeep.spec.ts b/src/fp/array/flatMapDeep.spec.ts new file mode 100644 index 000000000..af7b3e98b --- /dev/null +++ b/src/fp/array/flatMapDeep.spec.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { flatMapDeep } from './flatMapDeep'; + +describe('flatMapDeep', () => { + it("(non-curried) should map and deeply flatten an array", () => { + const result1 = flatMapDeep([1, 2, 3], n => [[n, n]]); + expect(result1).toEqual([1, 1, 2, 2, 3, 3]); + + const result2 = flatMapDeep([1, 2, 3], n => [[[n]], [[n]]]); + expect(result2).toEqual([1, 1, 2, 2, 3, 3]); + + const result3 = flatMapDeep([1, 2, 3], n => [n, [n, [n, [n]]]]); + expect(result3).toEqual([1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]); + }); + + it("(curried) should map and deeply flatten an array", () => { + const result1 = flatMapDeep(n => [[n, n]])([1, 2, 3]); + expect(result1).toEqual([1, 1, 2, 2, 3, 3]); + + const result2 = flatMapDeep(n => [[[n]], [[n]]])([1, 2, 3]); + expect(result2).toEqual([1, 1, 2, 2, 3, 3]); + + const result3 = flatMapDeep(n => [n, [n, [n, [n]]]])([1, 2, 3]); + expect(result3).toEqual([1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]); + }); + + it( + "(non-curried) should return an empty array when provided with an empty array", + () => { + const result = flatMapDeep([], n => [[n]]); + expect(result).toEqual([]); + } + ); + + it("(curried) should return an empty array when provided with an empty array", () => { + const result = flatMapDeep(n => [[n]])([]); + expect(result).toEqual([]); + }); +}); diff --git a/src/fp/array/flatMapDeep.ts b/src/fp/array/flatMapDeep.ts new file mode 100644 index 000000000..d8f06d519 --- /dev/null +++ b/src/fp/array/flatMapDeep.ts @@ -0,0 +1,40 @@ +import { ExtractNestedArrayType } from '../../array/flattenDeep.ts'; +import { flatMapDeep as flatMapDeepToolkit } from '../../array/flatMapDeep'; + +/** + * Recursively maps each element in an array using a provided iteratee function and then deeply flattens the resulting array. + * + * @template T - The type of elements within the array. + * @template U - The type of elements within the returned array from the iteratee function. + * @param {T[]} arr - The array to flatten. + * @param {(item: T) => U} iteratee - The function that produces the new array elements. + * @returns {Array>} A new array that has been flattened. + * + * @example + * const result = flatMapDeep([1, 2, 3], n => [[n, n]]); + * // [1, 1, 2, 2, 3, 3] + */ +export function flatMapDeep(arr: readonly T[], iteratee: (item: T) => U): Array>; + +/** + * Recursively maps each element in an array using a provided iteratee function and then deeply flattens the resulting array. + * + * @template T - The type of elements within the array. + * @template U - The type of elements within the returned array from the iteratee function. + * @param {(item: T) => U} iteratee - The function that produces the new array elements. + * @returns {(arr: T[]) => Array>} A function that receive the array to flatten as argument and returns a new array that has been flattened. + * + * @example + * const result = flatMapDeep(2, 3], n => [[n, n]])([1); + * // [1, 1, 2, 2, 3, 3] + */ +export function flatMapDeep(iteratee: (item: T) => U): (arr: readonly T[]) => Array>; + +export function flatMapDeep(arrOrIteratee: readonly T[] | ((item: T) => U), iteratee?: (item: T) => U) { + if (iteratee == null) { + return (arr: readonly T[]) => flatMapDeep(arr, arrOrIteratee as (item: T) => U); + } + + const arr = arrOrIteratee as readonly T[]; + return flatMapDeepToolkit(arr, iteratee); +} diff --git a/src/fp/array/forEach.spec.ts b/src/fp/array/forEach.spec.ts new file mode 100644 index 000000000..7d5e63f88 --- /dev/null +++ b/src/fp/array/forEach.spec.ts @@ -0,0 +1,82 @@ +import { describe, expect, it, vi } from 'vitest'; +import { forEach } from './forEach'; + +describe('forEach', () => { + it('(non-curried) should iterate over array elements', () => { + const arr = [1, 2, 3]; + const spy = vi.fn(); + forEach(arr, spy); + expect(spy).toHaveBeenCalledTimes(3); + expect(spy).toHaveBeenNthCalledWith(1, 1, 0, arr); + expect(spy).toHaveBeenNthCalledWith(2, 2, 1, arr); + expect(spy).toHaveBeenNthCalledWith(3, 3, 2, arr); + }); + + it('(curried) should iterate over array elements', () => { + const arr = [1, 2, 3]; + const spy = vi.fn(); + forEach(spy)(arr); + expect(spy).toHaveBeenCalledTimes(3); + expect(spy).toHaveBeenNthCalledWith(1, 1, 0, arr); + expect(spy).toHaveBeenNthCalledWith(2, 2, 1, arr); + expect(spy).toHaveBeenNthCalledWith(3, 3, 2, arr); + }); + + it('(non-curried) should work with different types', () => { + const arr = ['a', 'b', 'c']; + const spy = vi.fn(); + forEach(arr, spy); + expect(spy).toHaveBeenCalledTimes(3); + expect(spy).toHaveBeenNthCalledWith(1, 'a', 0, arr); + expect(spy).toHaveBeenNthCalledWith(2, 'b', 1, arr); + expect(spy).toHaveBeenNthCalledWith(3, 'c', 2, arr); + }); + + it('(curried) should work with different types', () => { + const arr = ['a', 'b', 'c']; + const spy = vi.fn(); + forEach(spy)(arr); + expect(spy).toHaveBeenCalledTimes(3); + expect(spy).toHaveBeenNthCalledWith(1, 'a', 0, arr); + expect(spy).toHaveBeenNthCalledWith(2, 'b', 1, arr); + expect(spy).toHaveBeenNthCalledWith(3, 'c', 2, arr); + }); + + it('(non-curried) should handle empty arrays', () => { + const spy = vi.fn(); + forEach([], spy); + expect(spy).not.toHaveBeenCalled(); + }); + + it('(curried) should handle empty arrays', () => { + const spy = vi.fn(); + forEach(spy)([]); + expect(spy).not.toHaveBeenCalled(); + }); + + it('(non-curried) should not modify the original array', () => { + const arr = [1, 2, 3]; + const original = [...arr]; + forEach(arr, value => value + 1); + expect(arr).toEqual(original); + }); + + it('(curried) should not modify the original array', () => { + const arr = [1, 2, 3]; + const original = [...arr]; + forEach(value => value + 1)(arr); + expect(arr).toEqual(original); + }); + + it('(non-curried) should allow mutation within the callback', () => { + const arr = [{ value: 1 }, { value: 2 }, { value: 3 }]; + forEach(arr, item => item.value++); + expect(arr).toEqual([{ value: 2 }, { value: 3 }, { value: 4 }]); + }); + + it('(curried) should allow mutation within the callback', () => { + const arr = [{ value: 1 }, { value: 2 }, { value: 3 }]; + forEach>(item => item.value++)(arr); + expect(arr).toEqual([{ value: 2 }, { value: 3 }, { value: 4 }]); + }); +}); diff --git a/src/fp/array/forEach.ts b/src/fp/array/forEach.ts new file mode 100644 index 000000000..261ba1460 --- /dev/null +++ b/src/fp/array/forEach.ts @@ -0,0 +1,47 @@ +/** + * Creates a function that iterates over elements of array and invokes iteratee for each element. + * + * @template T - The type of array. + * @param {(value: T[number], index: number, arr: T) => void} iteratee - The function invoked per iteration. + * @returns {(arr: T) => void} A function that takes an array and executes the iteratee function. + * + * @example + * const numbers = [1, 2, 3]; + * const logNumber = forEach((num) => console.log(num)); + * logNumber(numbers); + * // logs: 1, 2, 3 + */ +export function forEach( + iteratee: (value: T[number], index: number, arr: T) => void +): (arr: T) => void; +/** + * Iterates over elements of array and invokes iteratee for each element. + * + * @template T - The type of array. + * @param {T} arr - The array to iterate over. + * @param {(value: T[number], index: number, arr: T) => void} iteratee - The function invoked per iteration. + * @returns {void} + * + * @example + * const numbers = [1, 2, 3]; + * forEach(numbers, (num) => console.log(num)); + * // logs: 1, 2, 3 + */ +export function forEach( + arr: T, + iteratee: (value: T[number], index: number, arr: T) => void +): void; + +export function forEach( + arrOrIteratee: T | ((value: T[number], index: number, arr: T) => void), + iteratee?: (value: T[number], index: number, arr: T) => void +) { + if (iteratee == null) { + return (arr: T) => forEach(arr, arrOrIteratee as (value: T[number], index: number, arr: T) => void); + } + + const arr = arrOrIteratee as T; + for (let i = 0; i < arr.length; i++) { + iteratee(arr[i], i, arr); + } +} diff --git a/src/fp/array/forEachRight.spec.ts b/src/fp/array/forEachRight.spec.ts new file mode 100644 index 000000000..ad6893c59 --- /dev/null +++ b/src/fp/array/forEachRight.spec.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from 'vitest'; +import { forEachRight } from './forEachRight'; + +describe('forEachRight', () => { + it("(non-curried) should iterate over elements from right to left", () => { + const array = [1, 2, 3]; + const result: number[] = []; + + forEachRight(array, value => { + result.push(value); + }); + + expect(result).toEqual([3, 2, 1]); + }); + + it("(curried) should iterate over elements from right to left", () => { + const array = [1, 2, 3]; + const result: number[] = []; + + forEachRight(value => { + result.push(value); + })(array); + + expect(result).toEqual([3, 2, 1]); + }); + + it("(non-curried) should provide correct index and array for arrays", () => { + const array = [1, 2, 3]; + const indices: number[] = []; + const arrays: number[][] = []; + + forEachRight(array, (_, index, arr) => { + indices.push(index); + arrays.push(arr); + }); + + expect(indices).toEqual([2, 1, 0]); + expect(arrays).toEqual([array, array, array]); + }); + + it("(curried) should provide correct index and array for arrays", () => { + const array = [1, 2, 3]; + const indices: number[] = []; + const arrays: number[][] = []; + + forEachRight((_, index, arr) => { + indices.push(index); + arrays.push(arr); + })(array); + + expect(indices).toEqual([2, 1, 0]); + expect(arrays).toEqual([array, array, array]); + }); + + it("(non-curried) should handle an empty array", () => { + const array: number[] = []; + const result: number[] = []; + + forEachRight(array, value => { + result.push(value); + }); + + expect(result).toEqual([]); + }); + + it("(curried) should handle an empty array", () => { + const array: number[] = []; + const result: number[] = []; + + forEachRight(value => { + result.push(value); + })(array); + + expect(result).toEqual([]); + }); +}); diff --git a/src/fp/array/forEachRight.ts b/src/fp/array/forEachRight.ts new file mode 100644 index 000000000..4b877f054 --- /dev/null +++ b/src/fp/array/forEachRight.ts @@ -0,0 +1,110 @@ +import { forEachRight as forEachRightToolkit } from '../../array/forEachRight'; + +/** + * Iterates over elements of 'arr' from right to left and invokes 'callback' for each element. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array to iterate over. + * @param {(value: T, index: number, arr: T[]) => void} callback - The function invoked per iteration. + * The callback function receives three arguments: + * - 'value': The current element being processed in the array. + * - 'index': The index of the current element being processed in the array. + * - 'arr': The array 'forEachRight' was called upon. + * + * @example + * const array = [1, 2, 3]; + * const result: number[] = []; + * + * // Use the forEachRight function to iterate through the array and add each element to the result array. + * forEachRight(array, (value) => { + * result.push(value); + * }) + * + * console.log(result) // Output: [3, 2, 1] + */ +export function forEachRight(arr: T[], callback: (value: T, index: number, arr: T[]) => void): void; +/** + * Iterates over elements of 'arr' from right to left and invokes 'callback' for each element. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array to iterate over. + * @param {(value: T, index: number, arr: T[]) => void} callback - The function invoked per iteration. + * The callback function receives three arguments: + * - 'value': The current element being processed in the array. + * - 'index': The index of the current element being processed in the array. + * - 'arr': The array 'forEachRight' was called upon. + * + * @example + * const array = [1, 2, 3]; + * const result: number[] = []; + * + * // Use the forEachRight function to iterate through the array and add each element to the result array. + * forEachRight(array, (value) => { + * result.push(value); + * }) + * + * console.log(result) // Output: [3, 2, 1] + */ +export function forEachRight( + arr: readonly T[], + callback: (value: T, index: number, arr: readonly T[]) => void +): void; + +/** + * Iterates over elements of 'arr' from right to left and invokes 'callback' for each element. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array to iterate over. + * @param {(value: T, index: number, arr: T[]) => void} callback - The function invoked per iteration. + * The callback function receives three arguments: + * - 'value': The current element being processed in the array. + * - 'index': The index of the current element being processed in the array. + * - 'arr': The array 'forEachRight' was called upon. + * + * @example + * const array = [1, 2, 3]; + * const result: number[] = []; + * + * // Use the forEachRight function to iterate through the array and add each element to the result array. + * forEachRight(array, (value) => { + * result.push(value); + * }) + * + * console.log(result) // Output: [3, 2, 1] + */ +export function forEachRight(arr: readonly T[], callback: (value: T, index: number, arr: T[]) => void): void; + +/** + * Iterates over elements of 'arr' from right to left and invokes 'callback' for each element. + * + * @template T - The type of elements in the array. + * @param {(value: T, index: number, arr: T[]) => void} callback - The function invoked per iteration. + * The callback function receives three arguments: + * - 'value': The current element being processed in the array. + * - 'index': The index of the current element being processed in the array. + * - 'arr': The array 'forEachRight' was called upon. + * + * @example + * const array = [1, 2, 3]; + * const result: number[] = []; + * + * // Use the forEachRight function to iterate through the array and add each element to the result array. + * forEachRight(array, (value) => { + * result.push(value); + * }) + * + * console.log(result) // Output: [3, 2, 1] + */ +export function forEachRight(callback: (value: T, index: number, arr: T[]) => void): (arr: readonly T[]) => void; + +export function forEachRight( + arrOrCallback: readonly T[] | ((value: T, index: number, arr: T[]) => void), + callback?: (value: T, index: number, arr: T[]) => void +) { + if (callback == null) { + return (arr: readonly T[]) => forEachRight(arr, arrOrCallback as (value: T, index: number, arr: T[]) => void); + } + + const arr = arrOrCallback as T[]; + return forEachRightToolkit(arr, callback); +} diff --git a/src/fp/array/groupBy.spec.ts b/src/fp/array/groupBy.spec.ts new file mode 100644 index 000000000..25c631605 --- /dev/null +++ b/src/fp/array/groupBy.spec.ts @@ -0,0 +1,236 @@ +import { describe, expect, it } from 'vitest'; +import { groupBy } from './groupBy'; + +describe('groupBy', () => { + it("(non-curried) should group elements by a given key", () => { + const array = [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'banana' }, + { category: 'vegetable', name: 'carrot' }, + { category: 'fruit', name: 'pear' }, + { category: 'vegetable', name: 'broccoli' }, + ]; + + const result = groupBy(array, item => item.category); + + expect(result).toEqual({ + fruit: [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'banana' }, + { category: 'fruit', name: 'pear' }, + ], + vegetable: [ + { category: 'vegetable', name: 'carrot' }, + { category: 'vegetable', name: 'broccoli' }, + ], + }); + }); + + it("(curried) should group elements by a given key", () => { + const array = [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'banana' }, + { category: 'vegetable', name: 'carrot' }, + { category: 'fruit', name: 'pear' }, + { category: 'vegetable', name: 'broccoli' }, + ]; + + const result = groupBy<{ category: string; name: string }, string>(item => item.category)(array); + + expect(result).toEqual({ + fruit: [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'banana' }, + { category: 'fruit', name: 'pear' }, + ], + vegetable: [ + { category: 'vegetable', name: 'carrot' }, + { category: 'vegetable', name: 'broccoli' }, + ], + }); + }); + + it("(non-curried) should handle keys like `toString` or `valueOf", () => { + const array = [ + { method: 'toString', foo: 1 }, + { method: 'toString', foo: 2 }, + { method: 'valueOf', bar: 1 }, + { method: 'valueOf', bar: 2 }, + ]; + + expect(groupBy(array, x => x.method)).toEqual({ + toString: [ + { method: 'toString', foo: 1 }, + { method: 'toString', foo: 2 }, + ], + valueOf: [ + { method: 'valueOf', bar: 1 }, + { method: 'valueOf', bar: 2 }, + ], + }); + }); + + it("(curried) should handle keys like `toString` or `valueOf", () => { + const array = [ + { method: 'toString', foo: 1 }, + { method: 'toString', foo: 2 }, + { method: 'valueOf', bar: 1 }, + { method: 'valueOf', bar: 2 }, + ]; + + expect(groupBy<{ method: string; foo?: number, bar?: number }, string>(x => x.method)(array)).toEqual({ + toString: [ + { method: 'toString', foo: 1 }, + { method: 'toString', foo: 2 }, + ], + valueOf: [ + { method: 'valueOf', bar: 1 }, + { method: 'valueOf', bar: 2 }, + ], + }); + }); + + it("(non-curried) should handle an empty array", () => { + const array: Array<{ category: string; name: string }> = []; + + const result = groupBy(array, item => item.category); + + expect(result).toEqual({}); + }); + + it("(curried) should handle an empty array", () => { + const array: Array<{ category: string; name: string }> = []; + + const result = groupBy<{ category: string; name: string }, string>(item => item.category)(array); + + expect(result).toEqual({}); + }); + + it("(non-curried) should handle an array with one element", () => { + const array = [{ category: 'fruit', name: 'apple' }]; + + const result = groupBy(array, item => item.category); + + expect(result).toEqual({ + fruit: [{ category: 'fruit', name: 'apple' }], + }); + }); + + it("(curried) should handle an array with one element", () => { + const array = [{ category: 'fruit', name: 'apple' }]; + + const result = groupBy<{ category: string; name: string }, string>(item => item.category)(array); + + expect(result).toEqual({ + fruit: [{ category: 'fruit', name: 'apple' }], + }); + }); + + it("(non-curried) should group elements by a numeric key", () => { + const array = [ + { score: 1, name: 'John' }, + { score: 2, name: 'Jane' }, + { score: 1, name: 'Joe' }, + ]; + + const result = groupBy(array, item => item.score); + + expect(result).toEqual({ + '1': [ + { score: 1, name: 'John' }, + { score: 1, name: 'Joe' }, + ], + '2': [{ score: 2, name: 'Jane' }], + }); + }); + + it("(curried) should group elements by a numeric key", () => { + const array = [ + { score: 1, name: 'John' }, + { score: 2, name: 'Jane' }, + { score: 1, name: 'Joe' }, + ]; + + const result = groupBy<{ score: number; name: string }, number>(item => item.score)(array); + + expect(result).toEqual({ + '1': [ + { score: 1, name: 'John' }, + { score: 1, name: 'Joe' }, + ], + '2': [{ score: 2, name: 'Jane' }], + }); + }); + + it("(non-curried) should group elements by a symbol key", () => { + const TYPE_A = Symbol(); + const TYPE_B = Symbol(); + const array = [ + { type: TYPE_A, score: 1, name: 'John' }, + { type: TYPE_A, score: 2, name: 'Jane' }, + { type: TYPE_B, score: 1, name: 'Joe' }, + ]; + + const result = groupBy(array, item => item.type); + + expect(result).toEqual({ + [TYPE_A]: [ + { type: TYPE_A, score: 1, name: 'John' }, + { type: TYPE_A, score: 2, name: 'Jane' }, + ], + [TYPE_B]: [{ type: TYPE_B, score: 1, name: 'Joe' }], + }); + }); + + it("(curried) should group elements by a symbol key", () => { + const TYPE_A = Symbol(); + const TYPE_B = Symbol(); + const array = [ + { type: TYPE_A, score: 1, name: 'John' }, + { type: TYPE_A, score: 2, name: 'Jane' }, + { type: TYPE_B, score: 1, name: 'Joe' }, + ]; + + const result = groupBy<{ type: symbol; score: number; name: string }, symbol>(item => item.type)(array); + + expect(result).toEqual({ + [TYPE_A]: [ + { type: TYPE_A, score: 1, name: 'John' }, + { type: TYPE_A, score: 2, name: 'Jane' }, + ], + [TYPE_B]: [{ type: TYPE_B, score: 1, name: 'Joe' }], + }); + }); + + it("(non-curried) should handle duplicate keys correctly", () => { + const array = [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'apple' }, + ]; + + const result = groupBy(array, item => item.category); + + expect(result).toEqual({ + fruit: [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'apple' }, + ], + }); + }); + + it("(curried) should handle duplicate keys correctly", () => { + const array = [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'apple' }, + ]; + + const result = groupBy<{ category: string; name: string }, string>(item => item.category)(array); + + expect(result).toEqual({ + fruit: [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'apple' }, + ], + }); + }); +}); diff --git a/src/fp/array/groupBy.ts b/src/fp/array/groupBy.ts new file mode 100644 index 000000000..8846f1f83 --- /dev/null +++ b/src/fp/array/groupBy.ts @@ -0,0 +1,82 @@ +import { groupBy as groupByToolkit } from '../../array/groupBy'; + +/** + * Groups the elements of an array based on a provided key-generating function. + * + * This function takes an array and a function that generates a key from each element. It returns + * an object where the keys are the generated keys and the values are arrays of elements that share + * the same key. + * + * @template T - The type of elements in the array. + * @template K - The type of keys. + * @param {T[]} arr - The array to group. + * @param {(item: T) => K} getKeyFromItem - A function that generates a key from an element. + * @returns {Record} An object where each key is associated with an array of elements that + * share that key. + * + * @example + * const array = [ + * { category: 'fruit', name: 'apple' }, + * { category: 'fruit', name: 'banana' }, + * { category: 'vegetable', name: 'carrot' } + * ]; + * const result = groupBy(array, item => item.category); + * // result will be: + * // { + * // fruit: [ + * // { category: 'fruit', name: 'apple' }, + * // { category: 'fruit', name: 'banana' } + * // ], + * // vegetable: [ + * // { category: 'vegetable', name: 'carrot' } + * // ] + * // } + */ +export function groupBy(arr: readonly T[], getKeyFromItem: (item: T) => K): Record; + +/** + * Groups the elements of an array based on a provided key-generating function. + * + * This function takes an array and a function that generates a key from each element. It returns + * an object where the keys are the generated keys and the values are arrays of elements that share + * the same key. + * + * @template T - The type of elements in the array. + * @template K - The type of keys. + * @param {(item: T) => K} getKeyFromItem - A function that generates a key from an element. + * @returns {(arr: T[]) => Record} A function that receive the array to group as argument and returns an object where each key is associated with an array of elements that. + * share that key. + * + * @example + * const array = [ + * { category: 'fruit', name: 'apple' }, + * { category: 'fruit', name: 'banana' }, + * { category: 'vegetable', name: 'carrot' } + * ]; + * const result = groupBy(item => item.category)(array); + * // result will be: + * // { + * // fruit: [ + * // { category: 'fruit', name: 'apple' }, + * // { category: 'fruit', name: 'banana' } + * // ], + * // vegetable: [ + * // { category: 'vegetable', name: 'carrot' } + * // ] + * // } + */ +export function groupBy( + getKeyFromItem: (item: T) => K +): (arr: readonly T[]) => Record; + +export function groupBy( + arrOrGetKeyFromItem: readonly T[] | ((item: T) => K), + getKeyFromItem?: (item: T) => K +) { + if (getKeyFromItem == null) { + return (arr: readonly T[]) => groupBy(arr, arrOrGetKeyFromItem as (item: T) => K); + } + + const arr = arrOrGetKeyFromItem as readonly T[]; + return groupByToolkit(arr, getKeyFromItem); +} diff --git a/src/fp/array/includes.spec.ts b/src/fp/array/includes.spec.ts new file mode 100644 index 000000000..182b1e468 --- /dev/null +++ b/src/fp/array/includes.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { includes } from './includes'; + +describe('includes', () => { + it('should return true when element exists in array', () => { + expect(includes([1, 2, 3, 4], 2)).toBe(true); + expect(includes(2)([1, 2, 3, 4])).toBe(true); + }); + + it('should return false when element does not exist in array', () => { + expect(includes([1, 2, 3, 4], 5)).toBe(false); + expect(includes(5)([1, 2, 3, 4])).toBe(false); + }); + + it('should handle empty arrays', () => { + expect(includes([], 1)).toBe(false); + expect(includes(1)([])).toBe(false); + }); + + it('should work with different types', () => { + expect(includes(['a', 'b', 'c'], 'b')).toBe(true); + expect(includes('b')(['a', 'b', 'c'])).toBe(true); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + includes(arr, 2); + includes(2)(arr2); + + expect(arr).toEqual([1, 2, 3, 4]); + expect(arr2).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/src/fp/array/includes.ts b/src/fp/array/includes.ts new file mode 100644 index 000000000..872407211 --- /dev/null +++ b/src/fp/array/includes.ts @@ -0,0 +1,44 @@ +/** + * Determines whether an array includes a certain value. + * + * @template T - The type of array. + * @param {T[number]} searchElement - The value to search for. + * @returns {(arr: T) => boolean} A function that takes an array and returns whether the array includes the search element. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const hasTwo = includes(2); + * const result = hasTwo(arr); + * // result will be true + */ +export function includes(searchElement: T[number]): (arr: T) => boolean; +/** + * Determines whether an array includes a certain value. + * + * @template T - The type of array. + * @param {T} arr - The array to search in. + * @param {T[number]} searchElement - The value to search for. + * @returns {boolean} Whether the array includes the search element. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const result = includes(arr, 2); + * // result will be true + */ +export function includes(arr: T, searchElement: T[number]): boolean; + +export function includes(arrOrSearchElement: T | T[number], searchElement?: T[number]) { + if (searchElement === undefined && !Array.isArray(arrOrSearchElement)) { + return (arr: T) => includes(arr, arrOrSearchElement); + } + + const arr = arrOrSearchElement as T; + + for (const item of arr) { + if (item === searchElement) { + return true; + } + } + + return false; +} diff --git a/src/fp/array/intersection.spec.ts b/src/fp/array/intersection.spec.ts new file mode 100644 index 000000000..86d3b3561 --- /dev/null +++ b/src/fp/array/intersection.spec.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; +import { intersection } from './intersection'; + +describe('intersection', () => { + it("(non-curried) should return the intersection of two arrays", () => { + expect(intersection([1, 2], [1, 3])).toEqual([1]); + expect(intersection([1, 2], [3, 1])).toEqual([1]); + expect(intersection([1, 2], [3, 4])).toEqual([]); + expect(intersection([], [1, 2])).toEqual([]); + }); + + it("(curried) should return the intersection of two arrays", () => { + expect(intersection([1, 3])([1, 2])).toEqual([1]); + expect(intersection([3, 1])([1, 2])).toEqual([1]); + expect(intersection([3, 4])([1, 2])).toEqual([]); + expect(intersection([1, 2])([])).toEqual([]); + }); +}); diff --git a/src/fp/array/intersection.ts b/src/fp/array/intersection.ts new file mode 100644 index 000000000..c08b1edd8 --- /dev/null +++ b/src/fp/array/intersection.ts @@ -0,0 +1,49 @@ +import { intersection as intersectionToolkit } from '../../array/intersection'; + +/** + * Returns the intersection of two arrays. + * + * This function takes two arrays and returns a new array containing the elements that are + * present in both arrays. It effectively filters out any elements from the first array that + * are not found in the second array. + * + * @template T - The type of elements in the array. + * @param {T[]} firstArr - The first array to compare. + * @param {T[]} secondArr - The second array to compare. + * @returns {T[]} A new array containing the elements that are present in both arrays. + * + * @example + * const array1 = [1, 2, 3, 4, 5]; + * const array2 = [3, 4, 5, 6, 7]; + * const result = intersection(array1, array2); + * // result will be [3, 4, 5] since these elements are in both arrays. + */ +export function intersection(firstArr: readonly T[], secondArr: readonly T[]): T[]; + +/** + * Returns the intersection of two arrays. + * + * This function takes two arrays and returns a new array containing the elements that are + * present in both arrays. It effectively filters out any elements from the first array that + * are not found in the second array. + * + * @template T - The type of elements in the array. + * @param {T[]} secondArr - The second array to compare. + * @returns {(firstArr: T[]) => T[]} A function that receive the first array to compare as argument and returns a new array containing the elements that are present in both arrays. + * + * @example + * const array1 = [1, 2, 3, 4, 5]; + * const array2 = [3, 4, 5, 6, 7]; + * const result = intersection(array2)(array1); + * // result will be [3, 4, 5] since these elements are in both arrays. + */ +export function intersection(secondArr: readonly T[]): (firstArr: readonly T[]) => T[]; + +export function intersection(firstArrOrSecondArr: readonly T[] | readonly T[], secondArr?: readonly T[]) { + if (secondArr == null) { + return (firstArr: readonly T[]) => intersection(firstArr, firstArrOrSecondArr as readonly T[]); + } + + const firstArr = firstArrOrSecondArr as readonly T[]; + return intersectionToolkit(firstArr, secondArr); +} diff --git a/src/fp/array/intersectionBy.spec.ts b/src/fp/array/intersectionBy.spec.ts new file mode 100644 index 000000000..7e0f9cb32 --- /dev/null +++ b/src/fp/array/intersectionBy.spec.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest'; +import { intersectionBy } from './intersectionBy'; + +describe('intersectionBy', () => { + it('(non-curried) should return intersection of two arrays using mapper', () => { + const arr1 = [{ id: 1 }, { id: 2 }, { id: 3 }]; + const arr2 = [{ id: 2 }, { id: 4 }]; + expect(intersectionBy(arr1, arr2, item => item.id)).toEqual([{ id: 2 }]); + }); + + it('(curried) should return intersection of two arrays using mapper', () => { + const arr1 = [{ id: 1 }, { id: 2 }, { id: 3 }]; + const arr2 = [{ id: 2 }, { id: 4 }]; + expect(intersectionBy(arr2, item => item.id)(arr1)).toEqual([{ id: 2 }]); + }); + + it('(non-curried) should work with different types of values', () => { + const arr1 = [2.1, 1.2, 3.3]; + const arr2 = [2.3, 3.4]; + expect(intersectionBy(arr1, arr2, Math.floor)).toEqual([2.1, 3.3]); + }); + + it('(curried) should work with different types of values', () => { + const arr1 = [2.1, 1.2, 3.3]; + const arr2 = [2.3, 3.4]; + expect(intersectionBy(arr2, Math.floor)(arr1)).toEqual([2.1, 3.3]); + }); + + it('(non-curried) should return empty array when no intersection', () => { + const arr1 = [{ id: 1 }, { id: 2 }]; + const arr2 = [{ id: 3 }, { id: 4 }]; + expect(intersectionBy(arr1, arr2, item => item.id)).toEqual([]); + }); + + it('(curried) should return empty array when no intersection', () => { + const arr1 = [{ id: 1 }, { id: 2 }]; + const arr2 = [{ id: 3 }, { id: 4 }]; + expect(intersectionBy(arr2, item => item.id)(arr1)).toEqual([]); + }); + + it('(non-curried) should handle empty arrays', () => { + const arr1: Array<{ id: number }> = []; + const arr2 = [{ id: 1 }, { id: 2 }]; + expect(intersectionBy(arr1, arr2, item => item.id)).toEqual([]); + expect(intersectionBy(arr2, arr1, item => item.id)).toEqual([]); + }); + + it('(curried) should handle empty arrays', () => { + const arr1: Array<{ id: number }> = []; + const arr2 = [{ id: 1 }, { id: 2 }]; + expect(intersectionBy(arr2, item => item.id)(arr1)).toEqual([]); + expect(intersectionBy(arr1, item => item.id)(arr2)).toEqual([]); + }); + + it('(non-curried) should not modify original arrays', () => { + const arr1 = [{ id: 1 }, { id: 2 }]; + const arr2 = [{ id: 2 }, { id: 3 }]; + const original1 = [...arr1]; + const original2 = [...arr2]; + + intersectionBy(arr1, arr2, item => item.id); + + expect(arr1).toEqual(original1); + expect(arr2).toEqual(original2); + }); + + it('(curried) should not modify original arrays', () => { + const arr1 = [{ id: 1 }, { id: 2 }]; + const arr2 = [{ id: 2 }, { id: 3 }]; + const original1 = [...arr1]; + const original2 = [...arr2]; + + intersectionBy(arr2, item => item.id)(arr1); + + expect(arr1).toEqual(original1); + expect(arr2).toEqual(original2); + }); +}); diff --git a/src/fp/array/intersectionBy.ts b/src/fp/array/intersectionBy.ts new file mode 100644 index 000000000..05ab434a6 --- /dev/null +++ b/src/fp/array/intersectionBy.ts @@ -0,0 +1,55 @@ +import { intersectionBy as intersectionByToolkit } from '../../array/intersectionBy'; + +/** + * Creates a function that returns the intersection of two arrays based on a mapping function. + * + * @template T - The type of array. + * @template U - The type of mapped elements. + * @param {T} values - The values to inspect. + * @param {(value: T[number]) => U} mapper - The function to map the elements. + * @returns {(arr: T) => T} A function that takes an array and returns a new array with the intersection. + * + * @example + * const objects = [{ id: 1 }, { id: 2 }, { id: 3 }]; + * const intersectById = intersectionBy([{ id: 2 }, { id: 4 }], item => item.id); + * const result = intersectById(objects); + * // result will be [{ id: 2 }] + */ +export function intersectionBy( + values: T, + mapper: (value: T[number]) => U +): (arr: T) => T; +/** + * Returns the intersection of two arrays based on a mapping function. + * + * @template T - The type of array. + * @template U - The type of mapped elements. + * @param {T} arr - The array to inspect. + * @param {T} values - The values to inspect. + * @param {(value: T[number]) => U} mapper - The function to map the elements. + * @returns {T} A new array with the intersection. + * + * @example + * const objects = [{ id: 1 }, { id: 2 }, { id: 3 }]; + * const result = intersectionBy(objects, [{ id: 2 }, { id: 4 }], item => item.id); + * // result will be [{ id: 2 }] + */ +export function intersectionBy( + arr: T, + values: T, + mapper: (value: T[number]) => U +): T; + +export function intersectionBy( + arrOrValues: T, + valuesOrMapper: T | ((value: T[number]) => U), + mapper?: (value: T[number]) => U +) { + if (mapper === undefined && typeof valuesOrMapper === 'function') { + return (arr: T) => intersectionBy(arr, arrOrValues, valuesOrMapper); + } + + const arr = arrOrValues as T; + const values = valuesOrMapper as T; + return intersectionByToolkit(arr, values, mapper!) as T; +} diff --git a/src/fp/array/intersectionWith.spec.ts b/src/fp/array/intersectionWith.spec.ts new file mode 100644 index 000000000..e84e8649b --- /dev/null +++ b/src/fp/array/intersectionWith.spec.ts @@ -0,0 +1,98 @@ +import { describe, expect, it } from 'vitest'; +import { intersectionWith } from './intersectionWith'; + +describe('intersectionWith', () => { + it('(non-curried) should return intersection of two arrays using comparator', () => { + const arr1 = [{ x: 1, y: 1 }, { x: 2, y: 1 }]; + const arr2 = [{ x: 1, y: 2 }]; + expect( + intersectionWith(arr1, arr2, (a, b) => a.x + a.y === b.x + b.y) + ).toEqual([{ x: 2, y: 1 }]); + }); + + it('(curried) should return intersection of two arrays using comparator', () => { + const arr1 = [{ x: 1, y: 1 }, { x: 2, y: 1 }]; + const arr2 = [{ x: 1, y: 2 }]; + expect( + intersectionWith(arr2, (a, b) => a.x + a.y === b.x + b.y)(arr1) + ).toEqual([{ x: 2, y: 1 }]); + }); + + it('(non-curried) should work with different types of values', () => { + const arr1 = [1.2, 2.4, 3.6]; + const arr2 = [2.0, 3.0]; + expect( + intersectionWith(arr1, arr2, (a, b) => Math.floor(a) === Math.floor(b)) + ).toEqual([2.4, 3.6]); + }); + + it('(curried) should work with different types of values', () => { + const arr1 = [1.2, 2.4, 3.6]; + const arr2 = [2.0, 3.0]; + expect( + intersectionWith(arr2, (a, b) => Math.floor(a) === Math.floor(b))(arr1) + ).toEqual([2.4, 3.6]); + }); + + it('(non-curried) should return empty array when no intersection', () => { + const arr1 = [{ x: 1 }, { x: 2 }]; + const arr2 = [{ x: 3 }, { x: 4 }]; + expect( + intersectionWith(arr1, arr2, (a, b) => a.x === b.x) + ).toEqual([]); + }); + + it('(curried) should return empty array when no intersection', () => { + const arr1 = [{ x: 1 }, { x: 2 }]; + const arr2 = [{ x: 3 }, { x: 4 }]; + expect( + intersectionWith(arr2, (a, b) => a.x === b.x)(arr1) + ).toEqual([]); + }); + + it('(non-curried) should handle empty arrays', () => { + const arr1: Array<{ x: number }> = []; + const arr2 = [{ x: 1 }, { x: 2 }]; + expect( + intersectionWith(arr1, arr2, (a, b) => a.x === b.x) + ).toEqual([]); + expect( + intersectionWith(arr2, arr1, (a, b) => a.x === b.x) + ).toEqual([]); + }); + + it('(curried) should handle empty arrays', () => { + const arr1: Array<{ x: number }> = []; + const arr2 = [{ x: 1 }, { x: 2 }]; + expect( + intersectionWith(arr2, (a, b) => a.x === b.x)(arr1) + ).toEqual([]); + expect( + intersectionWith(arr1, (a, b) => a.x === b.x)(arr2) + ).toEqual([]); + }); + + it('(non-curried) should not modify original arrays', () => { + const arr1 = [{ x: 1 }, { x: 2 }]; + const arr2 = [{ x: 2 }, { x: 3 }]; + const original1 = [...arr1]; + const original2 = [...arr2]; + + intersectionWith(arr1, arr2, (a, b) => a.x === b.x); + + expect(arr1).toEqual(original1); + expect(arr2).toEqual(original2); + }); + + it('(curried) should not modify original arrays', () => { + const arr1 = [{ x: 1 }, { x: 2 }]; + const arr2 = [{ x: 2 }, { x: 3 }]; + const original1 = [...arr1]; + const original2 = [...arr2]; + + intersectionWith(arr2, (a, b) => a.x === b.x)(arr1); + + expect(arr1).toEqual(original1); + expect(arr2).toEqual(original2); + }); +}); diff --git a/src/fp/array/intersectionWith.ts b/src/fp/array/intersectionWith.ts new file mode 100644 index 000000000..7d90f214a --- /dev/null +++ b/src/fp/array/intersectionWith.ts @@ -0,0 +1,53 @@ +import { intersectionWith as intersectionWithToolkit } from '../../array/intersectionWith'; + +/** + * Creates a function that returns the intersection of two arrays using a comparator function. + * + * @template T - The type of array. + * @param {T} values - The values to inspect. + * @param {(a: T[number], b: T[number]) => boolean} comparator - The comparator invoked per element. + * @returns {(arr: T) => T} A function that takes an array and returns a new array with the intersection. + * + * @example + * const objects = [{ x: 1, y: 2 }, { x: 2, y: 1 }]; + * const intersectBySum = intersectionWith([{ x: 1, y: 1 }], (a, b) => a.x + a.y === b.x + b.y); + * const result = intersectBySum(objects); + * // result will be [{ x: 2, y: 1 }] + */ +export function intersectionWith( + values: T, + comparator: (a: T[number], b: T[number]) => boolean +): (arr: T) => T; +/** + * Returns the intersection of two arrays using a comparator function. + * + * @template T - The type of array. + * @param {T} arr - The array to inspect. + * @param {T} values - The values to inspect. + * @param {(a: T[number], b: T[number]) => boolean} comparator - The comparator invoked per element. + * @returns {T} A new array with the intersection. + * + * @example + * const objects = [{ x: 1, y: 2 }, { x: 2, y: 1 }]; + * const result = intersectionWith(objects, [{ x: 1, y: 1 }], (a, b) => a.x + a.y === b.x + b.y); + * // result will be [{ x: 2, y: 1 }] + */ +export function intersectionWith( + arr: T, + values: T, + comparator: (a: T[number], b: T[number]) => boolean +): T; + +export function intersectionWith( + arrOrValues: T, + valuesOrComparator: T | ((a: T[number], b: T[number]) => boolean), + comparator?: (a: T[number], b: T[number]) => boolean +) { + if (comparator === undefined && typeof valuesOrComparator === 'function') { + return (arr: T) => intersectionWith(arr, arrOrValues, valuesOrComparator); + } + + const arr = arrOrValues as T; + const values = valuesOrComparator as T; + return intersectionWithToolkit(arr, values, comparator!) as T; +} diff --git a/src/fp/array/isSubset.spec.ts b/src/fp/array/isSubset.spec.ts new file mode 100644 index 000000000..9e145980e --- /dev/null +++ b/src/fp/array/isSubset.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import { isSubset } from './isSubset'; + +describe('isSubset', () => { + it( + "(non-curried) should correctly determine if the subset arrays are subsets of the superset array", + () => { + const superset = [1, 2, 3, 4]; + const subset1 = [1, 3]; + const subset2 = [1, 5]; + const subset3 = [1, '3']; + + expect(isSubset(superset, subset1)).toBeTruthy(); + + expect(isSubset(superset, subset2)).toBeFalsy(); + expect(isSubset(superset, subset3)).toBeFalsy(); + } + ); + + it("(curried) should correctly determine if the subset arrays are subsets of the superset array", () => { + const superset = [1, 2, 3, 4]; + const subset1 = [1, 3]; + const subset2 = [1, 5]; + const subset3 = [1, '3']; + + expect(isSubset(subset1)(superset)).toBeTruthy(); + + expect(isSubset(subset2)(superset)).toBeFalsy(); + expect(isSubset(subset3)(superset)).toBeFalsy(); + }); +}); diff --git a/src/fp/array/isSubset.ts b/src/fp/array/isSubset.ts new file mode 100644 index 000000000..2405f9ea6 --- /dev/null +++ b/src/fp/array/isSubset.ts @@ -0,0 +1,60 @@ +import { isSubset as isSubsetToolkit } from '../../array/isSubset'; +import { difference } from '../array/difference.ts'; + +/** + * Checks if the `subset` array is entirely contained within the `superset` array. + * + * + * @template T - The type of elements contained in the arrays. + * @param {T[]} superset - The array that may contain all elements of the subset. + * @param {T[]} subset - The array to check against the superset. + * @returns {boolean} - Returns `true` if all elements of the `subset` are present in the `superset`, otherwise returns `false`. + * + * @example + * ```typescript + * const superset = [1, 2, 3, 4, 5]; + * const subset = [2, 3, 4]; + * isSubset(superset, subset); // true + * ``` + * + * @example + * ```typescript + * const superset = ['a', 'b', 'c']; + * const subset = ['a', 'd']; + * isSubset(superset, subset); // false + * ``` + */ +export function isSubset(superset: readonly T[], subset: readonly T[]): boolean; + +/** + * Checks if the `subset` array is entirely contained within the `superset` array. + * + * + * @template T - The type of elements contained in the arrays. + * @param {T[]} subset - The array to check against the superset. + * @returns {(superset: T[]) => boolean} A function that receive the array that may contain all elements of the subset as argument and returns - Returns `true` if all elements of the `subset` are present in the `superset`, otherwise returns `false`. + * + * @example + * ```typescript + * const superset = [1, 2, 3, 4, 5]; + * const subset = [2, 3, 4]; + * isSubset(subset)(superset); // true + * ``` + * + * @example + * ```typescript + * const superset = ['a', 'b', 'c']; + * const subset = ['a', 'd']; + * isSubset(superset, subset); // false + * ``` + */ +export function isSubset(subset: readonly T[]): (superset: readonly T[]) => boolean; + +export function isSubset(supersetOrSubset: readonly T[] | readonly T[], subset?: readonly T[]) { + if (subset == null) { + return (superset: readonly T[]) => isSubset(superset, supersetOrSubset as readonly T[]); + } + + const superset = supersetOrSubset as readonly T[]; + return isSubsetToolkit(superset, subset); +} diff --git a/src/fp/array/join.spec.ts b/src/fp/array/join.spec.ts new file mode 100644 index 000000000..a0ec7973d --- /dev/null +++ b/src/fp/array/join.spec.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { join } from './join'; + +describe('join', () => { + it('should join array elements with specified separator', () => { + expect(join([1, 2, 3, 4], ',')).toBe('1,2,3,4'); + expect(join(',')([1, 2, 3, 4])).toBe('1,2,3,4'); + }); + + it('should handle empty arrays', () => { + expect(join([], ',')).toBe(''); + expect(join(',')([])).toBe(''); + }); + + it('should work with different separators', () => { + expect(join([1, 2, 3], '')).toBe('123'); + expect(join('')([1, 2, 3])).toBe('123'); + expect(join([1, 2, 3], ' - ')).toBe('1 - 2 - 3'); + expect(join(' - ')([1, 2, 3])).toBe('1 - 2 - 3'); + }); + + it('should work with different types', () => { + expect(join(['a', 'b', 'c'], '+')).toBe('a+b+c'); + expect(join('+')(['a', 'b', 'c'])).toBe('a+b+c'); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + join(arr, ','); + join(',')(arr2); + + expect(arr).toEqual([1, 2, 3, 4]); + expect(arr2).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/src/fp/array/join.ts b/src/fp/array/join.ts new file mode 100644 index 000000000..2aae55628 --- /dev/null +++ b/src/fp/array/join.ts @@ -0,0 +1,37 @@ +/** + * Joins all elements of an array into a string with specified separator. + * + * @template T - The type of array. + * @param {string} separator - The string used to separate array elements. + * @returns {(arr: T) => string} A function that takes an array and returns a string with all elements joined. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const joinWithComma = join(','); + * const result = joinWithComma(arr); + * // result will be "1,2,3,4,5" + */ +export function join(separator: string): (arr: T) => string; +/** + * Joins all elements of an array into a string with specified separator. + * + * @template T - The type of array. + * @param {T} arr - The array to join. + * @param {string} separator - The string used to separate array elements. + * @returns {string} A string with all array elements joined. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const result = join(arr, ','); + * // result will be "1,2,3,4,5" + */ +export function join(arr: T, separator: string): string; + +export function join(arrOrSeparator: T | string, separator?: string) { + if (separator === undefined && typeof arrOrSeparator === 'string') { + return (arr: T) => join(arr, arrOrSeparator); + } + + const arr = arrOrSeparator as T; + return arr.join(separator); +} diff --git a/src/fp/array/keyBy.spec.ts b/src/fp/array/keyBy.spec.ts new file mode 100644 index 000000000..1102c4354 --- /dev/null +++ b/src/fp/array/keyBy.spec.ts @@ -0,0 +1,115 @@ +import { describe, expect, it } from 'vitest'; +import { keyBy } from './keyBy'; + +describe('keyBy', () => { + it("(non-curried) should map each element of an array by string key", () => { + const people = [ + { name: 'mike', age: 20 }, + { name: 'jake', age: 30 }, + ]; + + const result = keyBy(people, person => person.name); + expect(result).toEqual({ mike: { name: 'mike', age: 20 }, jake: { name: 'jake', age: 30 } }); + }); + + it("(curried) should map each element of an array by string key", () => { + const people = [ + { name: 'mike', age: 20 }, + { name: 'jake', age: 30 }, + ]; + + const result = keyBy<{ name: string; age: number }, string>(person => person.name)(people); + expect(result).toEqual({ mike: { name: 'mike', age: 20 }, jake: { name: 'jake', age: 30 } }); + }); + + it("(non-curried) should map each element of an array by number key", () => { + const people = [ + { name: 'mike', age: 20 }, + { name: 'jake', age: 30 }, + ]; + + const result = keyBy(people, person => person.age); + expect(result).toEqual({ 20: { name: 'mike', age: 20 }, 30: { name: 'jake', age: 30 } }); + }); + + it("(curried) should map each element of an array by number key", () => { + const people = [ + { name: 'mike', age: 20 }, + { name: 'jake', age: 30 }, + ]; + + const result = keyBy<{ name: string; age: number }, number>(person => person.age)(people); + expect(result).toEqual({ 20: { name: 'mike', age: 20 }, 30: { name: 'jake', age: 30 } }); + }); + + it("(non-curried) should map each element of an array by symbol key", () => { + const id1 = Symbol('id'); + const id2 = Symbol('id'); + const people = [ + { id: id1, name: 'mike', age: 20 }, + { id: id2, name: 'jake', age: 30 }, + ]; + + const result = keyBy(people, person => person.id); + expect(result).toEqual({ + [id1]: { id: id1, name: 'mike', age: 20 }, + [id2]: { id: id2, name: 'jake', age: 30 }, + }); + }); + + it("(curried) should map each element of an array by symbol key", () => { + const id1 = Symbol('id'); + const id2 = Symbol('id'); + const people = [ + { id: id1, name: 'mike', age: 20 }, + { id: id2, name: 'jake', age: 30 }, + ]; + + const result = keyBy<{ id: symbol; name: string; age: number }, symbol>(person => person.id)(people); + expect(result).toEqual({ + [id1]: { id: id1, name: 'mike', age: 20 }, + [id2]: { id: id2, name: 'jake', age: 30 }, + }); + }); + + it( + "(non-curried) should overwrite the value when encountering the same key", + () => { + const people = [ + { name: 'mike', age: 20 }, + { name: 'mike', age: 30 }, + ]; + + const result = keyBy(people, person => person.name); + + expect(result).toEqual({ mike: { name: 'mike', age: 30 } }); + } + ); + + it("(curried) should overwrite the value when encountering the same key", () => { + const people = [ + { name: 'mike', age: 20 }, + { name: 'mike', age: 30 }, + ]; + + const result = keyBy<{ name: string; age: number }, string>(person => person.name)(people); + + expect(result).toEqual({ mike: { name: 'mike', age: 30 } }); + }); + + it("(non-curried) should handle empty array", () => { + const people: Array<{ name: string; age: number }> = []; + + const result = keyBy(people, person => person.name); + + expect(result).toEqual({}); + }); + + it("(curried) should handle empty array", () => { + const people: Array<{ name: string; age: number }> = []; + + const result = keyBy<{ name: string; age: number }, string>(person => person.name)(people); + + expect(result).toEqual({}); + }); +}); diff --git a/src/fp/array/keyBy.ts b/src/fp/array/keyBy.ts new file mode 100644 index 000000000..c8cb9e59d --- /dev/null +++ b/src/fp/array/keyBy.ts @@ -0,0 +1,70 @@ +import { keyBy as keyByToolkit } from '../../array/keyBy'; + +/** + * Maps each element of an array based on a provided key-generating function. + * + * This function takes an array and a function that generates a key from each element. It returns + * an object where the keys are the generated keys and the values are the corresponding elements. + * If there are multiple elements generating the same key, the last element among them is used + * as the value. + * + * @template T - The type of elements in the array. + * @template K - The type of keys. + * @param {T[]} arr - The array of elements to be mapped. + * @param {(item: T) => K} getKeyFromItem - A function that generates a key from an element. + * @returns {Record} An object where keys are mapped to each element of an array. + * + * @example + * const array = [ + * { category: 'fruit', name: 'apple' }, + * { category: 'fruit', name: 'banana' }, + * { category: 'vegetable', name: 'carrot' } + * ]; + * const result = keyBy(array, item => item.category); + * // result will be: + * // { + * // fruit: { category: 'fruit', name: 'banana' }, + * // vegetable: { category: 'vegetable', name: 'carrot' } + * // } + */ +export function keyBy(arr: readonly T[], getKeyFromItem: (item: T) => K): Record; + +/** + * Maps each element of an array based on a provided key-generating function. + * + * This function takes an array and a function that generates a key from each element. It returns + * an object where the keys are the generated keys and the values are the corresponding elements. + * If there are multiple elements generating the same key, the last element among them is used + * as the value. + * + * @template T - The type of elements in the array. + * @template K - The type of keys. + * @param {(item: T) => K} getKeyFromItem - A function that generates a key from an element. + * @returns {(arr: T[]) => Record} A function that receive the array of elements to be mapped as argument and returns an object where keys are mapped to each element of an array. + * + * @example + * const array = [ + * { category: 'fruit', name: 'apple' }, + * { category: 'fruit', name: 'banana' }, + * { category: 'vegetable', name: 'carrot' } + * ]; + * const result = keyBy(item => item.category)(array); + * // result will be: + * // { + * // fruit: { category: 'fruit', name: 'banana' }, + * // vegetable: { category: 'vegetable', name: 'carrot' } + * // } + */ +export function keyBy(getKeyFromItem: (item: T) => K): (arr: readonly T[]) => Record; + +export function keyBy( + arrOrGetKeyFromItem: readonly T[] | ((item: T) => K), + getKeyFromItem?: (item: T) => K +) { + if (getKeyFromItem == null) { + return (arr: readonly T[]) => keyBy(arr, arrOrGetKeyFromItem as (item: T) => K); + } + + const arr = arrOrGetKeyFromItem as readonly T[]; + return keyByToolkit(arr, getKeyFromItem); +} diff --git a/src/fp/array/map.spec.ts b/src/fp/array/map.spec.ts new file mode 100644 index 000000000..2ca0dacb8 --- /dev/null +++ b/src/fp/array/map.spec.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest'; +import { map } from './map'; + +describe('map', () => { + it('should return mapped value by mapper function', () => { + expect(map([1, 2, 3], value => value * 2)).toEqual([2, 4, 6]); + expect(map(value => value * 2)([1, 2, 3])).toEqual([2, 4, 6]); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3]; + const arr2 = [1, 2, 3]; + + map(arr, value => value * 2); + map(value => value * 2)(arr2); + + expect(arr).toEqual([1, 2, 3]); + expect(arr2).toEqual([1, 2, 3]); + }); +}); diff --git a/src/fp/array/map.ts b/src/fp/array/map.ts new file mode 100644 index 000000000..14f3d80c4 --- /dev/null +++ b/src/fp/array/map.ts @@ -0,0 +1,48 @@ +/** + * Map each values of array by mapper function. + * + * @template T - The type of array. + * @template R - The type of mapped value. + * @param {(value: T[number]) => R} mapper - The function that map each items to new value. + * @returns {(arr: T) => R[]} A new array with mapped values. + * + * @example + * const arr = [1, 2, 3]; + * const double = map(value => value * 2); + * const result = double(arr); + * // result will be [2, 4, 6] + */ +export function map(mapper: (value: T[number]) => R): (arr: T) => R[]; +/** + * Map each values of array by mapper function. + * + * @template T - The type of array. + * @template R - The type of mapped value. + * @param {T} arr - The array to be mapped. + * @param {(value: T[number]) => R} mapper - The function that map each items to new value. + * @returns {R[]} A new array with mapped values. + * + * @example + * const arr = [1, 2, 3]; + * const result = map(arr, value => value * 2); + * // result will be [2, 4, 6] + */ +export function map(arr: T, mapper: (value: T[number]) => R): R[]; + +export function map( + arrOrMapper: T | ((value: T[number]) => R), + mapper?: (value: T[number]) => R +) { + if (mapper == null) { + return (arr: T) => map(arr, arrOrMapper as (value: T[number]) => R); + } + + const arr = arrOrMapper as T[]; + const result = []; + + for (const item of arr) { + result.push(mapper(item)); + } + + return result; +} diff --git a/src/fp/array/maxBy.spec.ts b/src/fp/array/maxBy.spec.ts new file mode 100644 index 000000000..5bb5f031f --- /dev/null +++ b/src/fp/array/maxBy.spec.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from 'vitest'; +import { maxBy } from './maxBy'; + +type Person = { name: string; age: number }; + +describe('maxBy', () => { + it("(non-curried) maxBy selects one max value in array", () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 20 }, + ]; + const result = maxBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 30 }); + }); + + it("(curried) maxBy selects one max value in array", () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 20 }, + ]; + const result = maxBy(person => person.age)(people); + expect(result).toEqual({ name: 'Nunu', age: 30 }); + }); + + it( + "(non-curried) if there are two max values, first one is selected", + () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 30 }, + ]; + const result = maxBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 30 }); + } + ); + + it("(curried) if there are two max values, first one is selected", () => { + const people = [ + { name: 'Mark', age: 25 }, + { name: 'Nunu', age: 30 }, + { name: 'Overmars', age: 30 }, + ]; + const result = maxBy(person => person.age)(people); + expect(result).toEqual({ name: 'Nunu', age: 30 }); + }); + + it( + "(non-curried) if array is single-element, return unique element of array", + () => { + const people = [{ name: 'Mark', age: 25 }]; + const result = maxBy(people, person => person.age); + expect(result).toEqual({ name: 'Mark', age: 25 }); + } + ); + + it("(curried) if array is single-element, return unique element of array", () => { + const people = [{ name: 'Mark', age: 25 }]; + const result = maxBy(person => person.age)(people); + expect(result).toEqual({ name: 'Mark', age: 25 }); + }); + + it("(non-curried) if array is empty, return undefined", () => { + const people: Person[] = []; + const result = maxBy(people, person => person.age); + expect(result).toBeUndefined(); + }); + + it("(curried) if array is empty, return undefined", () => { + const people: Person[] = []; + const result = maxBy(person => person.age)(people); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/fp/array/maxBy.ts b/src/fp/array/maxBy.ts new file mode 100644 index 000000000..7478e6b7a --- /dev/null +++ b/src/fp/array/maxBy.ts @@ -0,0 +1,56 @@ +import { maxBy as maxByToolkit } from '../../array/maxBy'; + +/** + * Creates a function that finds the element with the maximum value in an array + * using the provided value selector function. + * + * @template T - The type of array. + * @param {(element: T[number]) => number} getValue - The function to get the value to compare. + * @returns {(arr: T) => T['length'] extends 0 ? undefined : T[number]} A function that takes an array and returns the element with the maximum value. + * + * @example + * const objects = [{ value: 1 }, { value: 2 }, { value: 3 }]; + * const maxByValue = maxBy(obj => obj.value); + * const result = maxByValue(objects); + * // result will be { value: 3 } + */ +export function maxBy( + getValue: (element: T[number]) => number +): (arr: T) => T['length'] extends 0 ? undefined : T[number]; +export function maxBy(items: [T, ...T[]], getValue: (element: T) => number): T; +/** + * Finds the element in an array that has the maximum value when applying + * the `getValue` function to each element. + * + * @template T - The type of array. + * @param {T} arr The nonempty array of elements to search. + * @param {(element: T[number]) => number} getValue A function that selects a numeric value from each element. + * @returns {T['length'] extends 0 ? undefined : T[number]} The element with the maximum value as determined by the `getValue` function. + * @example + * maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 3 } + * maxBy([], x => x.a); // Returns: undefined + * maxBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'john', age: 30 } + */ +export function maxBy( + arr: T, + getValue: (element: T[number]) => number +): T['length'] extends 0 ? undefined : T[number]; + +export function maxBy( + arrOrGetValue: T | ((element: T[number]) => number), + getValue?: (element: T[number]) => number +) { + if (getValue === undefined && typeof arrOrGetValue === 'function') { + return (arr: T) => maxBy(arr, arrOrGetValue as (element: T[number]) => number); + } + + const arr = arrOrGetValue as T[]; + return maxByToolkit(arr, getValue!); +} diff --git a/src/fp/array/minBy.spec.ts b/src/fp/array/minBy.spec.ts new file mode 100644 index 000000000..bd981a3e4 --- /dev/null +++ b/src/fp/array/minBy.spec.ts @@ -0,0 +1,90 @@ +import { describe, expect, it } from 'vitest'; +import { minBy } from './minBy'; + +type Person = { name: string; age: number }; + +describe('minBy', () => { + it('(non-curried) should select one min value in array', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 35 }, + ]; + const result = minBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 20 }); + }); + + it('(curried) should select one min value in array', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 35 }, + ]; + const result = minBy(person => person.age)(people); + expect(result).toEqual({ name: 'Nunu', age: 20 }); + }); + + it('(non-curried) if there are two min values, first one is selected', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 20 }, + ]; + const result = minBy(people, person => person.age); + expect(result).toEqual({ name: 'Nunu', age: 20 }); + }); + + it('(curried) if there are two min values, first one is selected', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + { name: 'Overmars', age: 20 }, + ]; + const result = minBy(person => person.age)(people); + expect(result).toEqual({ name: 'Nunu', age: 20 }); + }); + + it('(non-curried) if array is single-element, return unique element of array', () => { + const people = [{ name: 'Mark', age: 25 }]; + const result = minBy(people, person => person.age); + expect(result).toEqual({ name: 'Mark', age: 25 }); + }); + + it('(curried) if array is single-element, return unique element of array', () => { + const people = [{ name: 'Mark', age: 25 }]; + const result = minBy(person => person.age)(people); + expect(result).toEqual({ name: 'Mark', age: 25 }); + }); + + it('(non-curried) if array is empty, return undefined', () => { + const people: Person[] = []; + const result = minBy(person => person.age)(people); + expect(result).toBeUndefined(); + }); + + it('(curried) if array is empty, return undefined', () => { + const people: Person[] = []; + const result = minBy(person => person.age)(people); + expect(result).toBeUndefined(); + }); + + it('(non-curried) should not modify original array', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + ]; + const original = [...people]; + minBy(people, person => person.age); + expect(people).toEqual(original); + }); + + it('(curried) should not modify original array', () => { + const people = [ + { name: 'Mark', age: 30 }, + { name: 'Nunu', age: 20 }, + ]; + const original = [...people]; + minBy(person => person.age)(people); + expect(people).toEqual(original); + }); +}); diff --git a/src/fp/array/minBy.ts b/src/fp/array/minBy.ts new file mode 100644 index 000000000..f2df74cd6 --- /dev/null +++ b/src/fp/array/minBy.ts @@ -0,0 +1,55 @@ +import { minBy as minByToolkit } from '../../array/minBy'; + +/** + * Creates a function that finds the element with the minimum value in an array + * using the provided value selector function. + * + * @template T - The type of array. + * @param {(element: T[number]) => number} getValue - The function to get the value to compare. + * @returns {(arr: T) => T['length'] extends 0 ? undefined : T[number]} A function that takes an array and returns the element with the minimum value. + * + * @example + * const objects = [{ value: 1 }, { value: 2 }, { value: 3 }]; + * const minByValue = minBy(obj => obj.value); + * const result = minByValue(objects); + * // result will be { value: 1 } + */ +export function minBy( + getValue: (element: T[number]) => number +): (arr: T) => T['length'] extends 0 ? undefined : T[number]; +/** + * Finds the element in an array that has the minimum value when applying + * the `getValue` function to each element. + * + * @template T - The type of array. + * @param {T} arr The nonempty array of elements to search. + * @param {(element: T[number]) => number} getValue A function that selects a numeric value from each element. + * @returns {T['length'] extends 0 ? undefined : T[number]} The element with the minimum value as determined by the `getValue` function. + * @example + * minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 1 } + * minBy([], x => x.a); // Returns: undefined + * minBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'joe', age: 26 } + */ +export function minBy( + arr: T, + getValue: (element: T[number]) => number +): T['length'] extends 0 ? undefined : T[number]; + +export function minBy( + arrOrGetValue: T | ((element: T[number]) => number), + getValue?: (element: T[number]) => number +) { + if (getValue === undefined && typeof arrOrGetValue === 'function') { + return (arr: T) => minBy(arr, arrOrGetValue as (element: T[number]) => number); + } + + const arr = arrOrGetValue as T[]; + return minByToolkit(arr, getValue!); +} diff --git a/src/fp/array/orderBy.spec.ts b/src/fp/array/orderBy.spec.ts new file mode 100644 index 000000000..0c9ace70f --- /dev/null +++ b/src/fp/array/orderBy.spec.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest'; +import { orderBy } from './orderBy'; + +type User = { name: string; age: number }; + +describe('orderBy', () => { + it('(non-curried) should sort by single property ascending', () => { + const users = [ + { name: 'fred', age: 48 }, + { name: 'barney', age: 34 }, + { name: 'fred', age: 40 }, + ]; + const result = orderBy(users, [user => user.age], ['asc']); + expect(result).toEqual([ + { name: 'barney', age: 34 }, + { name: 'fred', age: 40 }, + { name: 'fred', age: 48 }, + ]); + }); + + it('(curried) should sort by single property ascending', () => { + const users = [ + { name: 'fred', age: 48 }, + { name: 'barney', age: 34 }, + { name: 'fred', age: 40 }, + ]; + const result = orderBy([user => user.age], ['asc'])(users); + expect(result).toEqual([ + { name: 'barney', age: 34 }, + { name: 'fred', age: 40 }, + { name: 'fred', age: 48 }, + ]); + }); + + it('(non-curried) should sort by multiple properties with different orders', () => { + const users = [ + { name: 'fred', age: 48 }, + { name: 'barney', age: 34 }, + { name: 'fred', age: 40 }, + ]; + const result = orderBy( + users, + [user => user.name, user => user.age], + ['asc', 'desc'] + ); + expect(result).toEqual([ + { name: 'barney', age: 34 }, + { name: 'fred', age: 48 }, + { name: 'fred', age: 40 }, + ]); + }); + + it('(curried) should sort by multiple properties with different orders', () => { + const users = [ + { name: 'fred', age: 48 }, + { name: 'barney', age: 34 }, + { name: 'fred', age: 40 }, + ]; + const result = orderBy( + [user => user.name, user => user.age], + ['asc', 'desc'] + )(users); + expect(result).toEqual([ + { name: 'barney', age: 34 }, + { name: 'fred', age: 48 }, + { name: 'fred', age: 40 }, + ]); + }); + + it('(non-curried) should handle empty array', () => { + const users: User[] = []; + const result = orderBy(users, [user => user.age], ['asc']); + expect(result).toEqual([]); + }); + + it('(curried) should handle empty array', () => { + const users: User[] = []; + const result = orderBy([user => user.age], ['asc'])(users); + expect(result).toEqual([]); + }); + + it('(non-curried) should not modify original array', () => { + const users = [ + { name: 'fred', age: 48 }, + { name: 'barney', age: 34 }, + ]; + const original = [...users]; + orderBy(users, [user => user.age], ['asc']); + expect(users).toEqual(original); + }); + + it('(curried) should not modify original array', () => { + const users = [ + { name: 'fred', age: 48 }, + { name: 'barney', age: 34 }, + ]; + const original = [...users]; + orderBy([user => user.age], ['asc'])(users); + expect(users).toEqual(original); + }); +}); diff --git a/src/fp/array/orderBy.ts b/src/fp/array/orderBy.ts new file mode 100644 index 000000000..917ba6acb --- /dev/null +++ b/src/fp/array/orderBy.ts @@ -0,0 +1,71 @@ +import { orderBy as orderByToolkit } from '../../array/orderBy'; + +/** + * Creates a function that orders an array by one or more criteria. + * + * @template T - The type of array. + * @template U - The type of mapped elements. + * @param {Array<(item: T) => unknown | keyof U>} criteria - The criteria for sorting. + * @param {Array<'asc' | 'desc'>} orders - The sort orders of criteria. + * @returns {(arr: T) => T} A function that takes an array and returns a new sorted array. + * + * @example + * const users = [ + * { name: 'fred', age: 48 }, + * { name: 'barney', age: 34 }, + * { name: 'fred', age: 40 }, + * ]; + * const orderByNameAge = orderBy( + * [user => user.name, user => user.age], + * ['asc', 'desc'] + * ); + * const result = orderByNameAge(users); + * // => [{ name: 'barney', age: 34 }, { name: 'fred', age: 48 }, { name: 'fred', age: 40 }] + */ +export function orderBy( + criteria: Array<(item: T[number]) => unknown | keyof U>, + orders: Array<'asc' | 'desc'> +): (arr: T) => T; +/** + * Orders an array by one or more criteria. + * + * @template T - The type of array. + * @template U - The type of mapped elements. + * @param {T} arr - The array to sort. + * @param {Array<(item: T) => unknown | keyof U>} criteria - The criteria for sorting. + * @param {Array<'asc' | 'desc'>} orders - The sort orders of criteria. + * @returns {T} The new sorted array. + * + * @example + * const users = [ + * { name: 'fred', age: 48 }, + * { name: 'barney', age: 34 }, + * { name: 'fred', age: 40 }, + * ]; + * const result = orderBy( + * users, + * [user => user.name, user => user.age], + * ['asc', 'desc'] + * ); + * // => [{ name: 'barney', age: 34 }, { name: 'fred', age: 48 }, { name: 'fred', age: 40 }] + */ +export function orderBy( + arr: T, + criteria: Array<(item: T[number]) => unknown | keyof U>, + orders: Array<'asc' | 'desc'> +): T; + +export function orderBy( + arrOrIteratees: T | Array<(value: T[number]) => U>, + iterateesOrOrders: Array<(value: T[number]) => U> | Array<'asc' | 'desc'>, + orders?: Array<'asc' | 'desc'> +) { + if (orders === undefined && Array.isArray(iterateesOrOrders)) { + return (arr: T) => + orderBy(arr, arrOrIteratees as Array<(value: T[number]) => U>, iterateesOrOrders as Array<'asc' | 'desc'>); + } + + const arr = arrOrIteratees as T; + const iteratees = iterateesOrOrders as Array<(value: T[number]) => U>; + return orderByToolkit(arr as any, iteratees, orders!) as T; +} diff --git a/src/fp/array/partition.spec.ts b/src/fp/array/partition.spec.ts new file mode 100644 index 000000000..72973f634 --- /dev/null +++ b/src/fp/array/partition.spec.ts @@ -0,0 +1,74 @@ +import { describe, expect, expectTypeOf, it } from 'vitest'; +import { partition } from './partition'; + +describe('partition', () => { + it( + "(non-curried) creates two arrays: the first array includes items for which isInTruthy returns true, and the second array includes items for which isInTruthy returns false.", + () => { + expect(partition([true, true, false], x => x)).toEqual([[true, true], [false]]); + expect( + partition( + [ + { id: 1, enabled: true }, + { id: 2, enabled: false }, + { id: 3, enabled: false }, + { id: 4, enabled: true }, + ], + x => x.enabled + ) + ).toEqual([ + [ + { id: 1, enabled: true }, + { id: 4, enabled: true }, + ], + [ + { id: 2, enabled: false }, + { id: 3, enabled: false }, + ], + ]); + } + ); + + it("(curried) creates two arrays: the first array includes items for which isInTruthy returns true, and the second array includes items for which isInTruthy returns false.", () => { + expect(partition(x => x)([true, true, false])).toEqual([[true, true], [false]]); + expect( + partition<{ id: number; enabled: boolean }>(x => x.enabled)([ + { id: 1, enabled: true }, + { id: 2, enabled: false }, + { id: 3, enabled: false }, + { id: 4, enabled: true }, + ]) + ).toEqual([ + [ + { id: 1, enabled: true }, + { id: 4, enabled: true }, + ], + [ + { id: 2, enabled: false }, + { id: 3, enabled: false }, + ], + ]); + }); + + it("(non-curried) should correctly infer the type of a narrow array", () => { + const arr = [1, 2, 3, 4, 5] as const; + const [evens, odds] = partition(arr, num => num % 2 === 0); + + expect(evens).toEqual([2, 4]); + expect(odds).toEqual([1, 3, 5]); + + expectTypeOf(evens).toEqualTypeOf>(); + expectTypeOf(odds).toEqualTypeOf>(); + }); + + it("(curried) should correctly infer the type of a narrow array", () => { + const arr = [1, 2, 3, 4, 5] as const; + const [evens, odds] = partition(num => num % 2 === 0)(arr); + + expect(evens).toEqual([2, 4]); + expect(odds).toEqual([1, 3, 5]); + + expectTypeOf(evens).toEqualTypeOf>(); + expectTypeOf(odds).toEqualTypeOf>(); + }); +}); diff --git a/src/fp/array/partition.ts b/src/fp/array/partition.ts new file mode 100644 index 000000000..71b468e04 --- /dev/null +++ b/src/fp/array/partition.ts @@ -0,0 +1,60 @@ +import { partition as partitionToolkit } from '../../array/partition'; + +/** + * Splits an array into two groups based on a predicate function. + * + * This function takes an array and a predicate function. It returns a tuple of two arrays: + * the first array contains elements for which the predicate function returns true, and + * the second array contains elements for which the predicate function returns false. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array to partition. + * @param {(value: T) => boolean} isInTruthy - A predicate function that determines + * whether an element should be placed in the truthy array. The function is called with each + * element of the array. + * @returns {[T[], T[]]} A tuple containing two arrays: the first array contains elements for + * which the predicate returned true, and the second array contains elements for which the + * predicate returned false. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const isEven = x => x % 2 === 0; + * const [even, odd] = partition(array, isEven); + * // even will be [2, 4], and odd will be [1, 3, 5] + */ +export function partition(arr: readonly T[], isInTruthy: (value: T) => boolean): [truthy: T[], falsy: T[]]; + +/** + * Splits an array into two groups based on a predicate function. + * + * This function takes an array and a predicate function. It returns a tuple of two arrays: + * the first array contains elements for which the predicate function returns true, and + * the second array contains elements for which the predicate function returns false. + * + * @template T - The type of elements in the array. + * @param {(value: T) => boolean} isInTruthy - A predicate function that determines + * whether an element should be placed in the truthy array. The function is called with each + * element of the array. + * @returns {(arr: T[]) => [T[], T[]]} A function that receive the array to partition as argument and returns a tuple containing two arrays: the first array contains elements for. + * which the predicate returned true, and the second array contains elements for which the + * predicate returned false. + * + * @example + * const array = [1, 2, 3, 4, 5]; + * const isEven = x => x % 2 === 0; + * const [even, odd] = partition(isEven)(array); + * // even will be [2, 4], and odd will be [1, 3, 5] + */ +export function partition(isInTruthy: (value: T) => boolean): (arr: readonly T[]) => [truthy: T[], falsy: T[]]; + +export function partition( + arrOrIsInTruthy: readonly T[] | ((value: T) => boolean), + isInTruthy?: (value: T) => boolean +) { + if (isInTruthy == null) { + return (arr: readonly T[]) => partition(arr, arrOrIsInTruthy as (value: T) => boolean); + } + + const arr = arrOrIsInTruthy as readonly T[]; + return partitionToolkit(arr, isInTruthy); +} diff --git a/src/fp/array/pullAt.spec.ts b/src/fp/array/pullAt.spec.ts new file mode 100644 index 000000000..f027670ba --- /dev/null +++ b/src/fp/array/pullAt.spec.ts @@ -0,0 +1,137 @@ +import { describe, expect, it } from 'vitest'; +import { pullAt } from './pullAt'; + +describe('pullAt', () => { + it( + "(non-curried) should returns index searched of original array and changed original array", + () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt(array, [3, 5]); + + expect(array).toStrictEqual([0, 1, 2, 4]); + expect(result).toStrictEqual([3, 5]); + } + ); + + it("(curried) should returns index searched of original array and changed original array", () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt([3, 5])(array); + + expect(array).toStrictEqual([0, 1, 2, 4]); + expect(result).toStrictEqual([3, 5]); + }); + + it( + "(non-curried) even if there are duplicate index values must return an array containing duplicate index values", + () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt(array, [1, 2, 2, 4]); + + expect(array).toStrictEqual([0, 3, 5]); + expect(result).toStrictEqual([1, 2, 2, 4]); + } + ); + + it("(curried) even if there are duplicate index values must return an array containing duplicate index values", () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt([1, 2, 2, 4])(array); + + expect(array).toStrictEqual([0, 3, 5]); + expect(result).toStrictEqual([1, 2, 2, 4]); + }); + + it( + "(non-curried) even if there are not index value must return an array containing undefined value", + () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt(array, [0, 3, 5, 6]); + + expect(array).toStrictEqual([1, 2, 4]); + expect(result).toStrictEqual([0, 3, 5, undefined]); + } + ); + + it("(curried) even if there are not index value must return an array containing undefined value", () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt([0, 3, 5, 6])(array); + + expect(array).toStrictEqual([1, 2, 4]); + expect(result).toStrictEqual([0, 3, 5, undefined]); + }); + + it( + "(non-curried) even if there are other instance or type must return an array containing other instance or type values", + () => { + const array = [0, { a: 1 }, [2], 3, '4']; + const result = pullAt(array, [1, 2, 4]); + + expect(array).toStrictEqual([0, 3]); + expect(result).toStrictEqual([{ a: 1 }, [2], '4']); + } + ); + + it("(curried) even if there are other instance or type must return an array containing other instance or type values", () => { + const array = [0, { a: 1 }, [2], 3, '4']; + const result = pullAt([1, 2, 4])(array); + + expect(array).toStrictEqual([0, 3]); + expect(result).toStrictEqual([{ a: 1 }, [2], '4']); + }); + + it( + "(non-curried) even if index array parameter is empty must return an empty array", + () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt(array, []); + + expect(array).toStrictEqual([0, 1, 2, 3, 4, 5]); + expect(result).toStrictEqual([]); + } + ); + + it("(curried) even if index array parameter is empty must return an empty array", () => { + const array = [0, 1, 2, 3, 4, 5]; + const result = pullAt([])(array); + + expect(array).toStrictEqual([0, 1, 2, 3, 4, 5]); + expect(result).toStrictEqual([]); + }); + + it("(non-curried) should work with unsorted indexes", () => { + const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + const actual = pullAt(array, [1, 3, 11, 7, 5, 9]); + + expect(array).toEqual([1, 3, 5, 7, 9, 11]); + expect(actual).toEqual([2, 4, 12, 8, 6, 10]); + }); + + it("(curried) should work with unsorted indexes", () => { + const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + const actual = pullAt([1, 3, 11, 7, 5, 9])(array); + + expect(array).toEqual([1, 3, 5, 7, 9, 11]); + expect(actual).toEqual([2, 4, 12, 8, 6, 10]); + }); + + it("(non-curried) should work with objects", () => { + const foo = { foo: 1 }; + const bar = { foo: 2 }; + + const arr = [foo, bar, foo]; + const result = pullAt(arr, [2]); + + expect(arr).toEqual([foo, bar]); + expect(result).toEqual([foo]); + }); + + it("(curried) should work with objects", () => { + const foo = { foo: 1 }; + const bar = { foo: 2 }; + + const arr = [foo, bar, foo]; + const result = pullAt([2])(arr); + + expect(arr).toEqual([foo, bar]); + expect(result).toEqual([foo]); + }); +}); diff --git a/src/fp/array/pullAt.ts b/src/fp/array/pullAt.ts new file mode 100644 index 000000000..fb3c74b4d --- /dev/null +++ b/src/fp/array/pullAt.ts @@ -0,0 +1,50 @@ +import { at } from './at.ts'; +import { pullAt as pullAtToolkit } from '../../array/pullAt'; + +/** + * Removes elements from an array at specified indices and returns the removed elements. + * + * This function supports negative indices, which count from the end of the array. + * + * @template T + * @param {T[]} arr - The array from which elements will be removed. + * @param {number[]} indicesToRemove - An array of indices specifying the positions of elements to remove. + * @returns {Array} An array containing the elements that were removed from the original array. + * + * @example + * import { pullAt } from './pullAt'; + * + * const numbers = [10, 20, 30, 40, 50]; + * const removed = pullAt(numbers, [1, 3, 4]); + * console.log(removed); // [20, 40, 50] + * console.log(numbers); // [10, 30] + */ +export function pullAt(arr: T[], indicesToRemove: number[]): Array; + +/** + * Removes elements from an array at specified indices and returns the removed elements. + * + * This function supports negative indices, which count from the end of the array. + * + * @template T + * @param {number[]} indicesToRemove - An array of indices specifying the positions of elements to remove. + * @returns {(arr: T[]) => Array} A function that receive the array from which elements will be removed as argument and returns an array containing the elements that were removed from the original array. + * + * @example + * import { pullAt } from './pullAt'; + * + * const numbers = [10, 20, 30, 40, 50]; + * const removed = pullAt(numbers, [1, 3, 4]); + * console.log(removed); // [20, 40, 50] + * console.log(numbers); // [10, 30] + */ +export function pullAt(indicesToRemove: number[]): (arr: T[]) => Array; + +export function pullAt(arrOrIndicesToRemove: T[] | number[], indicesToRemove?: number[]) { + if (indicesToRemove == null) { + return (arr: T[]) => pullAt(arr, arrOrIndicesToRemove as number[]); + } + + const arr = arrOrIndicesToRemove as T[]; + return pullAtToolkit(arr, indicesToRemove); +} diff --git a/src/fp/array/sampleSize.spec.ts b/src/fp/array/sampleSize.spec.ts new file mode 100644 index 000000000..01526b722 --- /dev/null +++ b/src/fp/array/sampleSize.spec.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest'; +import { sampleSize } from './sampleSize'; + +describe('sampleSize', () => { + it( + "(non-curried) returns a sample element array of a specified size", + () => { + const array = [1, 2, 3]; + const result = sampleSize(array, 2); + + expect(array).toEqual(expect.arrayContaining(result)); + } + ); + + it("(curried) returns a sample element array of a specified size", () => { + const array = [1, 2, 3]; + const result = sampleSize(2)(array); + + expect(array).toEqual(expect.arrayContaining(result)); + }); + + it("(non-curried) returns an empty array for size 0", () => { + const result = sampleSize([1, 2, 3], 0); + expect(result).toEqual([]); + }); + + it("(curried) returns an empty array for size 0", () => { + const result = sampleSize(0)([1, 2, 3]); + expect(result).toEqual([]); + }); + + it( + "(non-curried) returns the same array if the size is equal to the array length", + () => { + const array = [1, 2, 3]; + const result = sampleSize(array, array.length); + + expect(result).toEqual(array); + expect(result).not.toBe(array); + } + ); + + it("(curried) returns the same array if the size is equal to the array length", () => { + const array = [1, 2, 3]; + const result = sampleSize(array.length)(array); + + expect(result).toEqual(array); + expect(result).not.toBe(array); + }); + + it( + "(non-curried) throws an error if the size is greater than the array length", + () => { + expect(() => sampleSize([1, 2, 3], 4)).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be less than or equal to the length of array.]` + ); + } + ); + + it("(curried) throws an error if the size is greater than the array length", () => { + expect(() => sampleSize(4)([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( + `[Error: Size must be less than or equal to the length of array.]` + ); + }); +}); diff --git a/src/fp/array/sampleSize.ts b/src/fp/array/sampleSize.ts new file mode 100644 index 000000000..912926c86 --- /dev/null +++ b/src/fp/array/sampleSize.ts @@ -0,0 +1,49 @@ +import { sampleSize as sampleSizeToolkit } from '../../array/sampleSize'; + +/** + * Returns a sample element array of a specified `size`. + * + * This function takes an array and a number, and returns an array containing the sampled elements using Floyd's algorithm. + * + * {@link https://www.nowherenearithaca.com/2013/05/robert-floyds-tiny-and-beautiful.html Floyd's algorithm} + * + * @template T - The type of elements in the array. + * @param {T[]} array - The array to sample from. + * @param {number} size - The size of sample. + * @returns {T[]} A new array with sample size applied. + * @throws {Error} Throws an error if `size` is greater than the length of `array`. + * + * @example + * const result = sampleSize([1, 2, 3], 2) + * // result will be an array containing two of the elements from the array. + * // [1, 2] or [1, 3] or [2, 3] + */ +export function sampleSize(array: readonly T[], size: number): T[]; + +/** + * Returns a sample element array of a specified `size`. + * + * This function takes an array and a number, and returns an array containing the sampled elements using Floyd's algorithm. + * + * {@link https://www.nowherenearithaca.com/2013/05/robert-floyds-tiny-and-beautiful.html Floyd's algorithm} + * + * @template T - The type of elements in the array. + * @param {number} size - The size of sample. + * @returns {(array: T[]) => T[]} A function that receive the array to sample from as argument and returns a new array with sample size applied. + * @throws {Error} Throws an error if `size` is greater than the length of `array`. + * + * @example + * const result = sampleSize([1, 2, 3], 2) + * // result will be an array containing two of the elements from the array. + * // [1, 2] or [1, 3] or [2, 3] + */ +export function sampleSize(size: number): (array: readonly T[]) => T[]; + +export function sampleSize(arrayOrSize: readonly T[] | number, size?: number) { + if (size == null) { + return (array: readonly T[]) => sampleSize(array, arrayOrSize as number); + } + + const array = arrayOrSize as readonly T[]; + return sampleSizeToolkit(array, size); +} diff --git a/src/fp/array/slice.spec.ts b/src/fp/array/slice.spec.ts new file mode 100644 index 000000000..bcc920424 --- /dev/null +++ b/src/fp/array/slice.spec.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import { slice } from './slice'; + +describe('slice', () => { + it('should return sliced array with start and end indices', () => { + expect(slice([1, 2, 3, 4, 5], 1, 3)).toEqual([2, 3]); + expect(slice(1, 3)([1, 2, 3, 4, 5])).toEqual([2, 3]); + }); + + it('should handle negative indices', () => { + expect(slice([1, 2, 3, 4, 5], -2)).toEqual([4, 5]); + expect(slice(-2)([1, 2, 3, 4, 5])).toEqual([4, 5]); + }); + + it('should handle omitted end index', () => { + expect(slice([1, 2, 3, 4, 5], 2)).toEqual([3, 4, 5]); + expect(slice(2)([1, 2, 3, 4, 5])).toEqual([3, 4, 5]); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3, 4, 5]; + const arr2 = [1, 2, 3, 4, 5]; + + slice(arr, 1, 3); + slice(1, 3)(arr2); + + expect(arr).toEqual([1, 2, 3, 4, 5]); + expect(arr2).toEqual([1, 2, 3, 4, 5]); + }); +}); diff --git a/src/fp/array/slice.ts b/src/fp/array/slice.ts new file mode 100644 index 000000000..95b49ba04 --- /dev/null +++ b/src/fp/array/slice.ts @@ -0,0 +1,40 @@ +/** + * Creates a slice of array from start up to end. + * + * @template T - The type of array. + * @param {number} [start=0] - The start position. Negative index counts from the end. + * @param {number} [end=array.length] - The end position. Negative index counts from the end. + * @returns {(arr: T) => T[number][]} A function that takes an array and returns sliced elements. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const getFirstThree = slice(0, 3); + * const result = getFirstThree(arr); + * // result will be [1, 2, 3] + */ +export function slice(start: number, end?: number): (arr: T) => T[number][]; +/** + * Creates a slice of array from start up to end. + * + * @template T - The type of array. + * @param {T} arr - The array to slice. + * @param {number} [start=0] - The start position. Negative index counts from the end. + * @param {number} [end=array.length] - The end position. Negative index counts from the end. + * @returns {T[number][]} A new array with extracted elements. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const result = slice(arr, 1, 3); + * // result will be [2, 3] + */ +export function slice(arr: T, startOrEnd: number, end?: number): T[number][]; + +export function slice(arrOrStart: T | number, startOrEnd?: number, end?: number) { + if (!Array.isArray(arrOrStart)) { + return (arr: T) => slice(arr, arrOrStart, startOrEnd); + } + + const arr = arrOrStart; + + return arr.slice(startOrEnd, end); +} diff --git a/src/fp/array/some.spec.ts b/src/fp/array/some.spec.ts new file mode 100644 index 000000000..ab3549282 --- /dev/null +++ b/src/fp/array/some.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { some } from './some'; + +describe('some', () => { + it('should return true when any element passes the test', () => { + expect(some([1, 2, 3, 4], value => value % 2 === 0)).toBe(true); + expect(some(value => value % 2 === 0)([1, 2, 3, 4])).toBe(true); + }); + + it('should return false when no element passes the test', () => { + expect(some([1, 3, 5, 7], value => value % 2 === 0)).toBe(false); + expect(some(value => value % 2 === 0)([1, 3, 5, 7])).toBe(false); + }); + + it('should handle empty arrays', () => { + expect(some([], () => true)).toBe(false); + expect(some(() => true)([])).toBe(false); + }); + + it('should work with different types', () => { + expect(some(['abc', 'def', 'ghi'], str => str.startsWith('d'))).toBe(true); + expect(some(str => str.startsWith('d'))(['abc', 'def', 'ghi'])).toBe(true); + }); + + it('should not change value of original array', () => { + const arr = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + some(arr, value => value > 2); + some(value => value > 2)(arr2); + + expect(arr).toEqual([1, 2, 3, 4]); + expect(arr2).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/src/fp/array/some.ts b/src/fp/array/some.ts new file mode 100644 index 000000000..6bedc6aec --- /dev/null +++ b/src/fp/array/some.ts @@ -0,0 +1,47 @@ +/** + * Tests whether at least one element in the array passes the test implemented by the provided function. + * + * @template T - The type of array. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {(arr: T) => boolean} A function that takes an array and returns whether any element passes the test. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const hasEven = some(value => value % 2 === 0); + * const result = hasEven(arr); + * // result will be true + */ +export function some(predicate: (value: T[number]) => boolean): (arr: T) => boolean; +/** + * Tests whether at least one element in the array passes the test implemented by the provided function. + * + * @template T - The type of array. + * @param {T} arr - The array to test. + * @param {(value: T[number]) => boolean} predicate - The function to test each element. + * @returns {boolean} Whether any element in the array passes the test. + * + * @example + * const arr = [1, 2, 3, 4, 5]; + * const result = some(arr, value => value % 2 === 0); + * // result will be true + */ +export function some(arr: T, predicate: (value: T[number]) => boolean): boolean; + +export function some( + arrOrPredicate: T | ((value: T[number]) => boolean), + predicate?: (value: T[number]) => boolean +) { + if (predicate == null) { + return (arr: T) => some(arr, arrOrPredicate as (value: T[number]) => boolean); + } + + const arr = arrOrPredicate as T; + + for (const item of arr) { + if (predicate(item)) { + return true; + } + } + + return false; +} diff --git a/src/fp/array/sort.spec.ts b/src/fp/array/sort.spec.ts new file mode 100644 index 000000000..fcb1a2a52 --- /dev/null +++ b/src/fp/array/sort.spec.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import { sort } from './sort'; + +describe('sort', () => { + it('should sort array in ascending order', () => { + expect(sort([3, 1, 4, 1, 5], (a, b) => a - b)).toEqual([1, 1, 3, 4, 5]); + expect(sort((a, b) => a - b)([3, 1, 4, 1, 5])).toEqual([1, 1, 3, 4, 5]); + }); + + it('should sort array in descending order', () => { + expect(sort([3, 1, 4, 1, 5], (a, b) => b - a)).toEqual([5, 4, 3, 1, 1]); + expect(sort((a, b) => b - a)([3, 1, 4, 1, 5])).toEqual([5, 4, 3, 1, 1]); + }); + + it('should work with different types', () => { + expect(sort(['c', 'a', 'b'], (a, b) => a.localeCompare(b))).toEqual(['a', 'b', 'c']); + expect(sort((a, b) => a.localeCompare(b))(['c', 'a', 'b'])).toEqual(['a', 'b', 'c']); + }); + + it('should not change value of original array', () => { + const arr = [3, 1, 4, 1, 5]; + const arr2 = [3, 1, 4, 1, 5]; + + sort(arr, (a, b) => a - b); + sort((a, b) => a - b)(arr2); + + expect(arr).toEqual([3, 1, 4, 1, 5]); + expect(arr2).toEqual([3, 1, 4, 1, 5]); + }); +}); diff --git a/src/fp/array/sort.ts b/src/fp/array/sort.ts new file mode 100644 index 000000000..7d9544376 --- /dev/null +++ b/src/fp/array/sort.ts @@ -0,0 +1,66 @@ +/** + * Sorts an array in ascending order. + * + * @template T - The type of array. + * @param {(a: T[number], b: T[number]) => number} comparator - The function to compare elements. + * @returns {(arr: T) => T} A function that takes an array and returns a new sorted array. + * + * @example + * const arr = [3, 1, 4, 1, 5]; + * const sortNumbers = sort((a, b) => a - b); + * const result = sortNumbers(arr); + * // result will be [1, 1, 3, 4, 5] + */ +export function sort(comparator: (a: T[number], b: T[number]) => number): (arr: T) => T; +/** + * Sorts an array in ascending order. + * + * @template T - The type of array. + * @param {T} arr - The array to sort. + * @param {(a: T[number], b: T[number]) => number} comparator - The function to compare elements. + * @returns {T} A new sorted array. + * + * @example + * const arr = [3, 1, 4, 1, 5]; + * const result = sort(arr, (a, b) => a - b); + * // result will be [1, 1, 3, 4, 5] + */ +export function sort(arr: T, comparator: (a: T[number], b: T[number]) => number): T; + +export function sort( + arrOrComparator: T | ((a: T[number], b: T[number]) => number), + comparator?: (a: T[number], b: T[number]) => number +) { + if (typeof arrOrComparator === 'function') { + return (arr: T) => sort(arr, arrOrComparator as (a: T[number], b: T[number]) => number); + } + + const arr = [...(arrOrComparator as T)]; + + // Quick sort implementation + const quickSort = (low: number, high: number) => { + if (low < high) { + const pivotIndex = partition(low, high); + quickSort(low, pivotIndex - 1); + quickSort(pivotIndex + 1, high); + } + }; + + const partition = (low: number, high: number) => { + const pivot = arr[high]; + let i = low - 1; + + for (let j = low; j < high; j++) { + if (comparator!(arr[j], pivot) <= 0) { + i++; + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + } + + [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]]; + return i + 1; + }; + + quickSort(0, arr.length - 1); + return arr; +} diff --git a/src/fp/array/sortBy.spec.ts b/src/fp/array/sortBy.spec.ts new file mode 100644 index 000000000..30f3dcf50 --- /dev/null +++ b/src/fp/array/sortBy.spec.ts @@ -0,0 +1,115 @@ +import { describe, expect, it } from 'vitest'; +import { sortBy } from './sortBy'; + +type User = { + user: string; + age: number; +}; + +describe('sortBy', () => { + const users: User[] = [ + { user: 'foo', age: 24 }, + { user: 'bar', age: 7 }, + { user: 'foo', age: 8 }, + { user: 'bar', age: 29 }, + ]; + + it( + "(non-curried) should stable sort objects by a single property in ascending order", + () => { + expect(sortBy(users, ['user'])).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 24 }, + { user: 'foo', age: 8 }, + ]); + } + ); + + it("(curried) should stable sort objects by a single property in ascending order", () => { + expect(sortBy(['user'])(users)).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 24 }, + { user: 'foo', age: 8 }, + ]); + }); + + it("(non-curried) should stable sort objects by multiple properties", () => { + expect(sortBy(users, ['user', 'age'])).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 8 }, + { user: 'foo', age: 24 }, + ]); + }); + + it("(curried) should stable sort objects by multiple properties", () => { + expect(sortBy(['user', 'age'])(users)).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 8 }, + { user: 'foo', age: 24 }, + ]); + }); + + it("(non-curried) should stable sort objects by iteratee function", () => { + expect(sortBy(users, [user => user.user])).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 24 }, + { user: 'foo', age: 8 }, + ]); + }); + + it("(curried) should stable sort objects by iteratee function", () => { + expect(sortBy([user => user.user])(users)).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 24 }, + { user: 'foo', age: 8 }, + ]); + }); + + it( + "(non-curried) should stable sort objects by iteratee function and property", + () => { + expect(sortBy(users, [user => user.user, user => user.age])).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 8 }, + { user: 'foo', age: 24 }, + ]); + } + ); + + it("(curried) should stable sort objects by iteratee function and property", () => { + expect(sortBy([user => user.user, user => user.age])(users)).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 8 }, + { user: 'foo', age: 24 }, + ]); + }); + + it( + "(non-curried) should stable sort objects by mixed iteratee function and key", + () => { + expect(sortBy(users, ['user', user => user.age])).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 8 }, + { user: 'foo', age: 24 }, + ]); + } + ); + + it("(curried) should stable sort objects by mixed iteratee function and key", () => { + expect(sortBy(['user', user => user.age])(users)).toEqual([ + { user: 'bar', age: 7 }, + { user: 'bar', age: 29 }, + { user: 'foo', age: 8 }, + { user: 'foo', age: 24 }, + ]); + }); +}); diff --git a/src/fp/array/sortBy.ts b/src/fp/array/sortBy.ts new file mode 100644 index 000000000..caacc5bd6 --- /dev/null +++ b/src/fp/array/sortBy.ts @@ -0,0 +1,81 @@ +import { orderBy } from './orderBy.ts'; +import { sortBy as sortByToolkit } from '../../array/sortBy'; + +/** + * Sorts an array of objects based on the given `criteria`. + * + * - If you provide keys, it sorts the objects by the values of those keys. + * - If you provide functions, it sorts based on the values returned by those functions. + * + * The function returns the array of objects sorted in ascending order. + * If two objects have the same value for the current criterion, it uses the next criterion to determine their order. + * + * @template T - The type of the objects in the array. + * @param {T[]} arr - The array of objects to be sorted. + * @param {Array<((item: T) => unknown) | keyof T>} criteria - The criteria for sorting. This can be an array of object keys or functions that return values used for sorting. + * @returns {T[]} - The sorted array. + * + * @example + * const users = [ + * { user: 'foo', age: 24 }, + * { user: 'bar', age: 7 }, + * { user: 'foo ', age: 8 }, + * { user: 'bar ', age: 29 }, + * ]; + * + * sortBy(users, ['user', 'age']); + * sortBy(users, [obj => obj.user, 'age']); + * // results will be: + * // [ + * // { user : 'bar', age: 7 }, + * // { user : 'bar', age: 29 }, + * // { user : 'foo', age: 8 }, + * // { user : 'foo', age: 24 }, + * // ] + */ +export function sortBy(arr: readonly T[], criteria: Array<((item: T) => unknown) | keyof T>): T[]; + +/** + * Sorts an array of objects based on the given `criteria`. + * + * - If you provide keys, it sorts the objects by the values of those keys. + * - If you provide functions, it sorts based on the values returned by those functions. + * + * The function returns the array of objects sorted in ascending order. + * If two objects have the same value for the current criterion, it uses the next criterion to determine their order. + * + * @template T - The type of the objects in the array. + * @param {Array<((item: T) => unknown) | keyof T>} criteria - The criteria for sorting. This can be an array of object keys or functions that return values used for sorting. + * @returns {(arr: T[]) => T[]} A function that receive the array of objects to be sorted as argument and returns - The sorted array. + * + * @example + * const users = [ + * { user: 'foo', age: 24 }, + * { user: 'bar', age: 7 }, + * { user: 'foo ', age: 8 }, + * { user: 'bar ', age: 29 }, + * ]; + * + * sortBy(['user', 'age'])(users); + * sortBy(users, [obj => obj.user, 'age']); + * // results will be: + * // [ + * // { user : 'bar', age: 7 }, + * // { user : 'bar', age: 29 }, + * // { user : 'foo', age: 8 }, + * // { user : 'foo', age: 24 }, + * // ] + */ +export function sortBy(criteria: Array<((item: T) => unknown) | keyof T>): (arr: readonly T[]) => T[]; + +export function sortBy( + arrOrCriteria: readonly T[] | Array<((item: T) => unknown) | keyof T>, + criteria?: Array<((item: T) => unknown) | keyof T> +) { + if (criteria == null) { + return (arr: readonly T[]) => sortBy(arr, arrOrCriteria as Array<((item: T) => unknown) | keyof T>); + } + + const arr = arrOrCriteria as readonly T[]; + return sortByToolkit(arr, criteria); +} diff --git a/src/fp/array/take.spec.ts b/src/fp/array/take.spec.ts new file mode 100644 index 000000000..028cd4bfa --- /dev/null +++ b/src/fp/array/take.spec.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; +import { take } from './take'; + +describe('take', () => { + it("(non-curried) returns the first n elements from the array", () => { + expect(take([1, 2, 3, 4, 5], 3)).toEqual([1, 2, 3]); + expect(take(['a', 'b', 'c', 'd'], 2)).toEqual(['a', 'b']); + expect(take([true, false, true], 1)).toEqual([true]); + }); + + it("(curried) returns the first n elements from the array", () => { + expect(take(3)([1, 2, 3, 4, 5])).toEqual([1, 2, 3]); + expect(take(2)(['a', 'b', 'c', 'd'])).toEqual(['a', 'b']); + expect(take(1)([true, false, true])).toEqual([true]); + }); + + it( + "(non-curried) handles cases where count is greater than array length", + () => { + expect(take([1, 2, 3], 5)).toEqual([1, 2, 3]); + } + ); + + it("(curried) handles cases where count is greater than array length", () => { + expect(take(5)([1, 2, 3])).toEqual([1, 2, 3]); + }); + + it("(non-curried) handles cases where count is zero", () => { + expect(take([1, 2, 3], 0)).toEqual([]); + }); + + it("(curried) handles cases where count is zero", () => { + expect(take(0)([1, 2, 3])).toEqual([]); + }); + + it("(non-curried) handles empty arrays", () => { + expect(take([], 3)).toEqual([]); + }); + + it("(curried) handles empty arrays", () => { + expect(take(3)([])).toEqual([]); + }); +}); diff --git a/src/fp/array/take.ts b/src/fp/array/take.ts new file mode 100644 index 000000000..b6d9c3e16 --- /dev/null +++ b/src/fp/array/take.ts @@ -0,0 +1,57 @@ +import { take as takeToolkit } from '../../array/take'; + +/** + * Returns a new array containing the first `count` elements from the input array `arr`. + * If `count` is greater than the length of `arr`, the entire array is returned. + * + * @template T - Type of elements in the input array. + * + * @param {T[]} arr - The array to take elements from. + * @param {number} count - The number of elements to take. + * @returns {T[]} A new array containing the first `count` elements from `arr`. + * + * @example + * // Returns [1, 2, 3] + * take([1, 2, 3, 4, 5], 3); + * + * @example + * // Returns ['a', 'b'] + * take(['a', 'b', 'c'], 2); + * + * @example + * // Returns [1, 2, 3] + * take([1, 2, 3], 5); + */ +export function take(arr: readonly T[], count: number): T[]; + +/** + * Returns a new array containing the first `count` elements from the input array `arr`. + * If `count` is greater than the length of `arr`, the entire array is returned. + * + * @template T - Type of elements in the input array. + * + * @param {number} count - The number of elements to take. + * @returns {(arr: T[]) => T[]} A function that receive the array to take elements from as argument and returns a new array containing the first `count` elements from `arr`. + * + * @example + * // Returns [1, 2, 3] + * take(2, 3, 4, 5], 3)([1); + * + * @example + * // Returns ['a', 'b'] + * take(['a', 'b', 'c'], 2); + * + * @example + * // Returns [1, 2, 3] + * take([1, 2, 3], 5); + */ +export function take(count: number): (arr: readonly T[]) => T[]; + +export function take(arrOrCount: readonly T[] | number, count?: number) { + if (count == null) { + return (arr: readonly T[]) => take(arr, arrOrCount as number); + } + + const arr = arrOrCount as readonly T[]; + return takeToolkit(arr, count); +} diff --git a/src/fp/array/takeRight.spec.ts b/src/fp/array/takeRight.spec.ts new file mode 100644 index 000000000..92a42fd0e --- /dev/null +++ b/src/fp/array/takeRight.spec.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest'; +import { takeRight } from './takeRight'; + +describe('takeRight', () => { + it("(non-curried) returns the last n elements from the array", () => { + expect(takeRight([1, 2, 3, 4, 5], 2)).toEqual([4, 5]); + expect(takeRight(['a', 'b', 'c', 'd'], 2)).toEqual(['c', 'd']); + expect(takeRight([true, false, true], 1)).toEqual([true]); + expect(takeRight([1, 2, 3], 5)).toEqual([1, 2, 3]); + expect(takeRight([1, 2, 3], 0)).toEqual([]); + expect(takeRight([], 3)).toEqual([]); + }); + + it("(curried) returns the last n elements from the array", () => { + expect(takeRight(2)([1, 2, 3, 4, 5])).toEqual([4, 5]); + expect(takeRight(2)(['a', 'b', 'c', 'd'])).toEqual(['c', 'd']); + expect(takeRight(1)([true, false, true])).toEqual([true]); + expect(takeRight(5)([1, 2, 3])).toEqual([1, 2, 3]); + expect(takeRight(0)([1, 2, 3])).toEqual([]); + expect(takeRight(3)([])).toEqual([]); + }); + + it( + "(non-curried) handles cases where count is greater than array length", + () => { + expect(takeRight([1, 2, 3], 5)).toEqual([1, 2, 3]); + } + ); + + it("(curried) handles cases where count is greater than array length", () => { + expect(takeRight(5)([1, 2, 3])).toEqual([1, 2, 3]); + }); + + it("(non-curried) handles cases where count is zero", () => { + expect(takeRight([1, 2, 3], 0)).toEqual([]); + }); + + it("(curried) handles cases where count is zero", () => { + expect(takeRight(0)([1, 2, 3])).toEqual([]); + }); + + it("(non-curried) handles empty arrays", () => { + expect(takeRight([], 3)).toEqual([]); + }); + + it("(curried) handles empty arrays", () => { + expect(takeRight(3)([])).toEqual([]); + }); +}); diff --git a/src/fp/array/takeRight.ts b/src/fp/array/takeRight.ts new file mode 100644 index 000000000..4bc2e48f9 --- /dev/null +++ b/src/fp/array/takeRight.ts @@ -0,0 +1,54 @@ +import { takeRight as takeRightToolkit } from "../../array/takeRight"; + +/** + * Returns a new array containing the last `count` elements from the input array `arr`. + * If `count` is greater than the length of `arr`, the entire array is returned. + * + * @template T - The type of elements in the array. + * @param {number} - The number of elements to take. + * @returns {(arr: T[]) =>T[]} A new array containing the last `count` elements from `arr`. + * + * @example + * // Returns [4, 5] + * takeRight(2)([1, 2, 3, 4, 5]); + * + * @example + * // Returns ['b', 'c'] + * takeRight(2)(['a', 'b', 'c']); + * + * @example + * // Returns [1, 2, 3] + * takeRight(5)([1, 2, 3]); + */ +export function takeRight(count: number): (arr: T[]) =>T[]; + +/** + * Returns a new array containing the last `count` elements from the input array `arr`. + * If `count` is greater than the length of `arr`, the entire array is returned. + * + * @template T - The type of elements in the array. + * @param {number} - The number of elements to take. + * @returns {T[]} A function that receive the array to take elements from as an argument and returns new array containing the last `count` elements from `arr`. + * + * @example + * // Returns [4, 5] + * takeRight([1, 2, 3, 4, 5], 2); + * + * @example + * // Returns ['b', 'c'] + * takeRight(['a', 'b', 'c'], 2); + * + * @example + * // Returns [1, 2, 3] + * takeRight([1, 2, 3], 5); + */ +export function takeRight(arr: T[], count: number): T[]; + +export function takeRight(arrOrCount: T[] | number, count?: number) { + if(count == null) { + return (arr: T[]) => takeRight(arr, arrOrCount as number); + } + + const arr = arrOrCount as T[]; + return takeRightToolkit(arr, count); +} diff --git a/src/fp/array/takeRightWhile.spec.ts b/src/fp/array/takeRightWhile.spec.ts new file mode 100644 index 000000000..16c733335 --- /dev/null +++ b/src/fp/array/takeRightWhile.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import { takeRightWhile } from './takeRightWhile'; + +describe('takeRightWhile', () => { + it( + "(non-curried) returns elements from the end while the predicate returns true", + () => { + expect(takeRightWhile([5, 4, 3, 2, 1], n => n < 4)).toEqual([3, 2, 1]); + expect(takeRightWhile([1, 2, 3], n => n > 3)).toEqual([]); + expect(takeRightWhile([1, 2, 3, 4, 5], n => n > 2)).toEqual([3, 4, 5]); + expect(takeRightWhile([1, 2, 3, 4, 5], n => n < 6)).toEqual([1, 2, 3, 4, 5]); + expect(takeRightWhile([], n => n < 6)).toEqual([]); + } + ); + + it("(curried) returns elements from the end while the predicate returns true", () => { + expect(takeRightWhile(n => n < 4)([5, 4, 3, 2, 1])).toEqual([3, 2, 1]); + expect(takeRightWhile(n => n > 3)([1, 2, 3])).toEqual([]); + expect(takeRightWhile(n => n > 2)([1, 2, 3, 4, 5])).toEqual([3, 4, 5]); + expect(takeRightWhile(n => n < 6)([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]); + expect(takeRightWhile(n => n < 6)([])).toEqual([]); + }); + + it("(non-curried) handles empty arrays", () => { + expect(takeRightWhile([], n => n < 6)).toEqual([]); + }); + + it("(curried) handles empty arrays", () => { + expect(takeRightWhile(n => n < 6)([])).toEqual([]); + }); +}); diff --git a/src/fp/array/takeRightWhile.ts b/src/fp/array/takeRightWhile.ts new file mode 100644 index 000000000..d3f1d7d30 --- /dev/null +++ b/src/fp/array/takeRightWhile.ts @@ -0,0 +1,50 @@ +import { takeRightWhile as takeRightWhileToolkit } from '../../array/takeRightWhile'; + +/** + * Takes elements from the end of the array while the predicate function returns `true`. + * + * @template T - Type of elements in the input array. + * + * @param {T[]} arr - The array to take elements from. + * @param {(item: T) => boolean} shouldContinueTaking - The function invoked per element. + * @returns {T[]} A new array containing the elements taken from the end while the predicate returns `true`. + * + * @example + * // Returns [3, 2, 1] + * takeRightWhile([5, 4, 3, 2, 1], n => n < 4); + * + * @example + * // Returns [] + * takeRightWhile([1, 2, 3], n => n > 3); + */ +export function takeRightWhile(arr: readonly T[], shouldContinueTaking: (item: T) => boolean): T[]; + +/** + * Takes elements from the end of the array while the predicate function returns `true`. + * + * @template T - Type of elements in the input array. + * + * @param {(item: T) => boolean} shouldContinueTaking - The function invoked per element. + * @returns {(arr: T[]) => T[]} A function that receive the array to take elements from as argument and returns a new array containing the elements taken from the end while the predicate returns `true`. + * + * @example + * // Returns [3, 2, 1] + * takeRightWhile(4, 3, 2, 1], n => n < 4)([5); + * + * @example + * // Returns [] + * takeRightWhile([1, 2, 3], n => n > 3); + */ +export function takeRightWhile(shouldContinueTaking: (item: T) => boolean): (arr: readonly T[]) => T[]; + +export function takeRightWhile( + arrOrShouldContinueTaking: readonly T[] | ((item: T) => boolean), + shouldContinueTaking?: (item: T) => boolean +) { + if (shouldContinueTaking == null) { + return (arr: readonly T[]) => takeRightWhile(arr, arrOrShouldContinueTaking as (item: T) => boolean); + } + + const arr = arrOrShouldContinueTaking as readonly T[]; + return takeRightWhileToolkit(arr, shouldContinueTaking); +} diff --git a/src/fp/array/takeWhile.spec.ts b/src/fp/array/takeWhile.spec.ts new file mode 100644 index 000000000..de83b4f21 --- /dev/null +++ b/src/fp/array/takeWhile.spec.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest'; +import { takeWhile } from './takeWhile'; + +// adjust the import path as necessary + +describe('takeWhile', () => { + it( + "(non-curried) should return elements while the predicate is true", + () => { + const arr = [1, 2, 3, 4, 5]; + const result = takeWhile(arr, x => x < 4); + expect(result).toEqual([1, 2, 3]); + } + ); + + it("(curried) should return elements while the predicate is true", () => { + const arr = [1, 2, 3, 4, 5]; + const result = takeWhile(x => x < 4)(arr); + expect(result).toEqual([1, 2, 3]); + }); + + it( + "(non-curried) should return an empty array if the first element does not satisfy the predicate", + () => { + const arr = [1, 2, 3, 4, 5]; + const result = takeWhile(arr, x => x > 4); + expect(result).toEqual([]); + } + ); + + it("(curried) should return an empty array if the first element does not satisfy the predicate", () => { + const arr = [1, 2, 3, 4, 5]; + const result = takeWhile(x => x > 4)(arr); + expect(result).toEqual([]); + }); + + it( + "(non-curried) should return all elements if all satisfy the predicate", + () => { + const arr = [1, 2, 3]; + const result = takeWhile(arr, x => x < 4); + expect(result).toEqual([1, 2, 3]); + } + ); + + it("(curried) should return all elements if all satisfy the predicate", () => { + const arr = [1, 2, 3]; + const result = takeWhile(x => x < 4)(arr); + expect(result).toEqual([1, 2, 3]); + }); + + it( + "(non-curried) should return an empty array if the input array is empty", + () => { + const arr: number[] = []; + const result = takeWhile(arr, x => x < 4); + expect(result).toEqual([]); + } + ); + + it("(curried) should return an empty array if the input array is empty", () => { + const arr: number[] = []; + const result = takeWhile(x => x < 4)(arr); + expect(result).toEqual([]); + }); + + it("(non-curried) should handle complex predicate functions", () => { + const arr = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; + const result = takeWhile(arr, item => item.id < 3); + expect(result).toEqual([{ id: 1 }, { id: 2 }]); + }); + + it("(curried) should handle complex predicate functions", () => { + const arr = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; + const result = takeWhile<{ id: number }>(item => item.id < 3)(arr); + expect(result).toEqual([{ id: 1 }, { id: 2 }]); + }); +}); diff --git a/src/fp/array/takeWhile.ts b/src/fp/array/takeWhile.ts new file mode 100644 index 000000000..3873e5bcb --- /dev/null +++ b/src/fp/array/takeWhile.ts @@ -0,0 +1,52 @@ +import { takeWhile as takeWhileToolkit } from '../../array/takeWhile'; + +/** + * Returns a new array containing the leading elements of the provided array + * that satisfy the provided predicate function. It stops taking elements as soon + * as an element does not satisfy the predicate. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array to process. + * @param {(element: T) => boolean} shouldContinueTaking - The predicate function that is called with each element. Elements are included in the result as long as this function returns true. + * @returns {T[]} A new array containing the leading elements that satisfy the predicate. + * + * @example + * // Returns [1, 2] + * takeWhile([1, 2, 3, 4], x => x < 3); + * + * @example + * // Returns [] + * takeWhile([1, 2, 3, 4], x => x > 3); + */ +export function takeWhile(arr: readonly T[], shouldContinueTaking: (element: T) => boolean): T[]; + +/** + * Returns a new array containing the leading elements of the provided array + * that satisfy the provided predicate function. It stops taking elements as soon + * as an element does not satisfy the predicate. + * + * @template T - The type of elements in the array. + * @param {(element: T) => boolean} shouldContinueTaking - The predicate function that is called with each element. Elements are included in the result as long as this function returns true. + * @returns {(arr: T[]) => T[]} A function that receive the array to process as argument and returns a new array containing the leading elements that satisfy the predicate. + * + * @example + * // Returns [1, 2] + * takeWhile(2, 3, 4], x => x < 3)([1); + * + * @example + * // Returns [] + * takeWhile([1, 2, 3, 4], x => x > 3); + */ +export function takeWhile(shouldContinueTaking: (element: T) => boolean): (arr: readonly T[]) => T[]; + +export function takeWhile( + arrOrShouldContinueTaking: readonly T[] | ((element: T) => boolean), + shouldContinueTaking?: (element: T) => boolean +) { + if (shouldContinueTaking == null) { + return (arr: readonly T[]) => takeWhile(arr, arrOrShouldContinueTaking as (element: T) => boolean); + } + + const arr = arrOrShouldContinueTaking as readonly T[]; + return takeWhileToolkit(arr, shouldContinueTaking); +} diff --git a/src/fp/array/union.spec.ts b/src/fp/array/union.spec.ts new file mode 100644 index 000000000..a14b7d642 --- /dev/null +++ b/src/fp/array/union.spec.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest'; +import { union } from './union'; + +describe('union', () => { + it("(non-curried) should return the union of two arrays", () => { + expect(union([2, 1], [2, 3])).toEqual([2, 1, 3]); + }); + + it("(curried) should return the union of two arrays", () => { + expect(union([2, 3])([2, 1])).toEqual([2, 1, 3]); + }); +}); diff --git a/src/fp/array/union.ts b/src/fp/array/union.ts new file mode 100644 index 000000000..b26c84d5e --- /dev/null +++ b/src/fp/array/union.ts @@ -0,0 +1,47 @@ +import { union as unionToolkit } from '../../array/union'; + +/** + * Creates an array of unique values from all given arrays. + * + * This function takes two arrays, merges them into a single array, and returns a new array + * containing only the unique values from the merged array. + * + * @template T - The type of elements in the array. + * @param {T[]} arr1 - The first array to merge and filter for unique values. + * @param {T[]} arr2 - The second array to merge and filter for unique values. + * @returns {T[]} A new array of unique values. + * + * @example + * const array1 = [1, 2, 3]; + * const array2 = [3, 4, 5]; + * const result = union(array1, array2); + * // result will be [1, 2, 3, 4, 5] + */ +export function union(arr1: readonly T[], arr2: readonly T[]): T[]; + +/** + * Creates an array of unique values from all given arrays. + * + * This function takes two arrays, merges them into a single array, and returns a new array + * containing only the unique values from the merged array. + * + * @template T - The type of elements in the array. + * @param {T[]} arr2 - The second array to merge and filter for unique values. + * @returns {(arr1: T[]) => T[]} A function that receive the first array to merge and filter for unique values as argument and returns a new array of unique values. + * + * @example + * const array1 = [1, 2, 3]; + * const array2 = [3, 4, 5]; + * const result = union(array2)(array1); + * // result will be [1, 2, 3, 4, 5] + */ +export function union(arr2: readonly T[]): (arr1: readonly T[]) => T[]; + +export function union(arr1OrArr2: readonly T[] | readonly T[], arr2?: readonly T[]) { + if (arr2 == null) { + return (arr1: readonly T[]) => union(arr1, arr1OrArr2 as readonly T[]); + } + + const arr1 = arr1OrArr2 as readonly T[]; + return unionToolkit(arr1, arr2); +} diff --git a/src/fp/array/unionBy.spec.ts b/src/fp/array/unionBy.spec.ts new file mode 100644 index 000000000..feb926df1 --- /dev/null +++ b/src/fp/array/unionBy.spec.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; +import { unionBy } from './unionBy'; + +describe('unionBy', () => { + it('(non-curried) should create a duplicate-free array by iteratee', () => { + const array = [{ x: 1 }, { x: 2 }, { x: 1 }]; + const result = unionBy(array, [{ x: 2 }, { x: 3 }], value => value.x); + expect(result).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); + }); + + it('(curried) should create a duplicate-free array by iteratee', () => { + const array = [{ x: 1 }, { x: 2 }, { x: 1 }]; + const result = unionBy([{ x: 2 }, { x: 3 }], value => value.x)(array); + expect(result).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); + }); + + it('(non-curried) should handle empty arrays', () => { + const result = unionBy([], [], (value: number) => value); + expect(result).toEqual([]); + }); + + it('(curried) should handle empty arrays', () => { + const result = unionBy([], (value: number) => value)([]); + expect(result).toEqual([]); + }); + + it('(non-curried) should not modify original arrays', () => { + const array1 = [{ x: 1 }, { x: 2 }]; + const array2 = [{ x: 2 }, { x: 3 }]; + const original1 = [...array1]; + const original2 = [...array2]; + unionBy(array1, array2, value => value.x); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); + + it('(curried) should not modify original arrays', () => { + const array1 = [{ x: 1 }, { x: 2 }]; + const array2 = [{ x: 2 }, { x: 3 }]; + const original1 = [...array1]; + const original2 = [...array2]; + unionBy(array2, value => value.x)(array1); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); +}); diff --git a/src/fp/array/unionBy.ts b/src/fp/array/unionBy.ts new file mode 100644 index 000000000..9415a0776 --- /dev/null +++ b/src/fp/array/unionBy.ts @@ -0,0 +1,42 @@ +import { unionBy as unionByToolkit } from '../../array/unionBy'; + +/** + * Creates a function that creates a new array of unique values by the iteratee function. + * + * @template T - The type of elements in the array. + * @template U - The type of the mapped elements. + * @param {Array} values - The values to include in the union. + * @param {(value: T) => U} iteratee - The function to map the elements. + * @returns {(arr: Array) => Array} A function that takes an array and returns a new array with unique values. + * + * @example + * const array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }]; + * const unionByX = unionBy([{ 'x': 2 }, { 'x': 3 }], value => value.x); + * const result = unionByX(array); + * // => [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }] + */ +export function unionBy(values: readonly T[], iteratee: (value: T) => U): (arr: readonly T[]) => T[]; + +/** + * Creates a new array of unique values by the iteratee function. + * + * @template T - The type of elements in the array. + * @template U - The type of the mapped elements. + * @param {Array} arr - The array to inspect. + * @param {Array} values - The values to include in the union. + * @param {(value: T) => U} iteratee - The function to map the elements. + * @returns {Array} The new array of combined unique values. + */ +export function unionBy(arr: readonly T[], values: readonly T[], iteratee: (value: T) => U): T[]; + +export function unionBy( + arrOrValues: readonly T[], + valuesOrIteratee: readonly T[] | ((value: T) => U), + iteratee?: (value: T) => U +) { + if (typeof valuesOrIteratee === 'function') { + return (arr: readonly T[]) => unionBy(arr, arrOrValues, valuesOrIteratee); + } + + return unionByToolkit(arrOrValues, valuesOrIteratee, iteratee!); +} diff --git a/src/fp/array/unionWith.spec.ts b/src/fp/array/unionWith.spec.ts new file mode 100644 index 000000000..62a08622b --- /dev/null +++ b/src/fp/array/unionWith.spec.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest'; +import { unionWith } from './unionWith'; + +describe('unionWith', () => { + const isEqual = (a: { x: number; y: number }, b: { x: number; y: number }) => + a.x === b.x && a.y === b.y; + + it('(non-curried) should create a duplicate-free array using comparator', () => { + const objects = [{ x: 1, y: 2 }, { x: 2, y: 1 }]; + const others = [{ x: 1, y: 1 }, { x: 1, y: 2 }]; + const result = unionWith(objects, others, isEqual); + expect(result).toEqual([ + { x: 1, y: 2 }, + { x: 2, y: 1 }, + { x: 1, y: 1 }, + ]); + }); + + it('(curried) should create a duplicate-free array using comparator', () => { + const objects = [{ x: 1, y: 2 }, { x: 2, y: 1 }]; + const others = [{ x: 1, y: 1 }, { x: 1, y: 2 }]; + const result = unionWith(others, isEqual)(objects); + expect(result).toEqual([ + { x: 1, y: 2 }, + { x: 2, y: 1 }, + { x: 1, y: 1 }, + ]); + }); + + it('(non-curried) should handle empty arrays', () => { + const result = unionWith([], [], isEqual); + expect(result).toEqual([]); + }); + + it('(curried) should handle empty arrays', () => { + const result = unionWith([], isEqual)([]); + expect(result).toEqual([]); + }); + + it('(non-curried) should not modify original arrays', () => { + const array1 = [{ x: 1, y: 2 }]; + const array2 = [{ x: 1, y: 1 }]; + const original1 = [...array1]; + const original2 = [...array2]; + unionWith(array1, array2, isEqual); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); + + it('(curried) should not modify original arrays', () => { + const array1 = [{ x: 1, y: 2 }]; + const array2 = [{ x: 1, y: 1 }]; + const original1 = [...array1]; + const original2 = [...array2]; + unionWith(array2, isEqual)(array1); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); +}); diff --git a/src/fp/array/unionWith.ts b/src/fp/array/unionWith.ts new file mode 100644 index 000000000..0ffabecb4 --- /dev/null +++ b/src/fp/array/unionWith.ts @@ -0,0 +1,42 @@ +import { unionWith as unionWithToolkit } from '../../array/unionWith'; + +/** + * Creates a function that creates a new array of unique values using a comparator function. + * + * @template T - The type of elements in the array. + * @param {Array} values - The values to include in the union. + * @param {(a: T, b: T) => boolean} comparator - The function to compare elements. + * @returns {(arr: Array) => Array} A function that takes an array and returns a new array with unique values. + * + * @example + * const objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * const others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * const isEqual = (a, b) => a.x === b.x && a.y === b.y; + * const unionWithEqual = unionWith(others, isEqual); + * const result = unionWithEqual(objects); + * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] + */ +export function unionWith(values: readonly T[], comparator: (a: T, b: T) => boolean): (arr: readonly T[]) => T[]; + +/** + * Creates a new array of unique values using a comparator function. + * + * @template T - The type of elements in the array. + * @param {Array} arr - The array to inspect. + * @param {Array} values - The values to include in the union. + * @param {(a: T, b: T) => boolean} comparator - The function to compare elements. + * @returns {Array} The new array of combined unique values. + */ +export function unionWith(arr: readonly T[], values: readonly T[], comparator: (a: T, b: T) => boolean): T[]; + +export function unionWith( + arrOrValues: readonly T[], + valuesOrComparator: readonly T[] | ((a: T, b: T) => boolean), + comparator?: (a: T, b: T) => boolean +) { + if (typeof valuesOrComparator === 'function') { + return (arr: readonly T[]) => unionWith(arr, arrOrValues, valuesOrComparator); + } + + return unionWithToolkit(arrOrValues, valuesOrComparator, comparator!); +} diff --git a/src/fp/array/uniq.spec.ts b/src/fp/array/uniq.spec.ts new file mode 100644 index 000000000..dc013fec1 --- /dev/null +++ b/src/fp/array/uniq.spec.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { uniq } from './uniq'; + +describe('uniq', () => { + it('uniq function creates unique elements from the array passed as an argument.', () => { + expect(uniq([11, 2, 3, 44, 11, 2, 3])).toEqual([11, 2, 3, 44]); + }); + it('uniq function works with strings.', () => { + expect(uniq(['a', 'b', 'b', 'c', 'a'])).toEqual(['a', 'b', 'c']); + }); + it('uniq function works with boolean values.', () => { + expect(uniq([true, false, true, false, false])).toEqual([true, false]); + }); + it('uniq function works with nullish values.', () => { + expect(uniq([null, undefined, null, undefined])).toEqual([null, undefined]); + }); + it('uniq function works with empty arrays.', () => { + expect(uniq([])).toEqual([]); + }); + it('uniq function works with multiple types.', () => { + expect(uniq([1, 'a', 2, 'b', 1, 'a'])).toEqual([1, 'a', 2, 'b']); + }); + it('uniq function keeps its original order.', () => { + expect(uniq([1, 2, 2, 3, 4, 4, 5])).toEqual([1, 2, 3, 4, 5]); + }); + it('uniq function should create a new array.', () => { + const array = [1, 2, 3]; + const result = uniq(array); + + expect(result).toEqual([1, 2, 3]); + expect(result).not.toBe(array); + }); + it('uniq function should not mutate the original array.', () => { + const array = [1, 2, 3, 2, 1, 3]; + uniq(array); + + expect(array).toEqual([1, 2, 3, 2, 1, 3]); + }); +}); diff --git a/src/fp/array/uniq.ts b/src/fp/array/uniq.ts new file mode 100644 index 000000000..5e486338e --- /dev/null +++ b/src/fp/array/uniq.ts @@ -0,0 +1,18 @@ +/** + * Creates a duplicate-free version of an array. + * + * This function takes an array and returns a new array containing only the unique values + * from the original array, preserving the order of first occurrence. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array to process. + * @returns {T[]} A new array with only unique values from the original array. + * + * @example + * const array = [1, 2, 2, 3, 4, 4, 5]; + * const result = uniq(array); + * // result will be [1, 2, 3, 4, 5] + */ +export function uniq(arr: readonly T[]): T[] { + return Array.from(new Set(arr)); +} diff --git a/src/fp/array/uniqBy.spec.ts b/src/fp/array/uniqBy.spec.ts new file mode 100644 index 000000000..e01c4d03e --- /dev/null +++ b/src/fp/array/uniqBy.spec.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest'; +import { uniqBy } from './uniqBy'; + +describe('uniqBy', () => { + it("(non-curried) should work with a `mapper`", () => { + expect(uniqBy([2.1, 1.2, 2.3], Math.floor)).toEqual([2.1, 1.2]); + expect(uniqBy([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], Math.floor)).toEqual([1.2, 2.1, 3.2, 5.7, 7.19]); + }); + + it("(curried) should work with a `mapper`", () => { + expect(uniqBy(Math.floor)([2.1, 1.2, 2.3])).toEqual([2.1, 1.2]); + expect(uniqBy(Math.floor)([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19])).toEqual([1.2, 2.1, 3.2, 5.7, 7.19]); + }); +}); diff --git a/src/fp/array/uniqBy.ts b/src/fp/array/uniqBy.ts new file mode 100644 index 000000000..e1cf487d5 --- /dev/null +++ b/src/fp/array/uniqBy.ts @@ -0,0 +1,65 @@ +import { uniqBy as uniqByToolkit } from '../../array/uniqBy'; + +/** + * Returns a new array containing only the unique elements from the original array, + * based on the values returned by the mapper function. + * + * @template T - The type of elements in the array. + * @template U - The type of mapped elements. + * @param {T[]} arr - The array to process. + * @param {(item: T) => U} mapper - The function used to convert the array elements. + * @returns {T[]} A new array containing only the unique elements from the original array, based on the values returned by the mapper function. + * + * @example + * ```ts + * uniqBy([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], Math.floor); + * // [1.2, 2.1, 3.2, 5.7, 7.19] + * ``` + * + * @example + * const array = [ + * { category: 'fruit', name: 'apple' }, + * { category: 'fruit', name: 'banana' }, + * { category: 'vegetable', name: 'carrot' }, + * ]; + * uniqBy(array, item => item.category).length + * // 2 + * ``` + */ +export function uniqBy(arr: readonly T[], mapper: (item: T) => U): T[]; + +/** + * Returns a new array containing only the unique elements from the original array, + * based on the values returned by the mapper function. + * + * @template T - The type of elements in the array. + * @template U - The type of mapped elements. + * @param {(item: T) => U} mapper - The function used to convert the array elements. + * @returns {(arr: T[]) => T[]} A function that receive the array to process as argument and returns a new array containing only the unique elements from the original array, based on the values returned by the mapper function. + * + * @example + * ```ts + * uniqBy(1.5, 2.1, 3.2, 5.7, 5.3, 7.19], Math.floor)([1.2); + * // [1.2, 2.1, 3.2, 5.7, 7.19] + * ``` + * + * @example + * const array = [ + * { category: 'fruit', name: 'apple' }, + * { category: 'fruit', name: 'banana' }, + * { category: 'vegetable', name: 'carrot' }, + * ]; + * uniqBy(array, item => item.category).length + * // 2 + * ``` + */ +export function uniqBy(mapper: (item: T) => U): (arr: readonly T[]) => T[]; + +export function uniqBy(arrOrMapper: readonly T[] | ((item: T) => U), mapper?: (item: T) => U) { + if (mapper == null) { + return (arr: readonly T[]) => uniqBy(arr, arrOrMapper as (item: T) => U); + } + + const arr = arrOrMapper as readonly T[]; + return uniqByToolkit(arr, mapper); +} diff --git a/src/fp/array/uniqWith.spec.ts b/src/fp/array/uniqWith.spec.ts new file mode 100644 index 000000000..45e67491c --- /dev/null +++ b/src/fp/array/uniqWith.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import { uniqWith } from './uniqWith'; + +describe('uniqWith', () => { + it("(non-curried) should work with a `comparator`", () => { + expect( + uniqWith( + [ + { x: 1, y: 2 }, + { x: 1, y: 3 }, + ], + (a, b) => a.x === b.x + ) + ).toEqual([{ x: 1, y: 2 }]); + expect(uniqWith([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], (a, b) => Math.abs(a - b) < 1)).toEqual([ + 1.2, 3.2, 5.7, 7.19, + ]); + }); + + it("(curried) should work with a `comparator`", () => { + expect( + uniqWith<{ x: number; y: number }>((a, b) => a.x === b.x)([ + { x: 1, y: 2 }, + { x: 1, y: 3 }, + ]) + ).toEqual([{ x: 1, y: 2 }]); + expect(uniqWith((a, b) => Math.abs(a - b) < 1)([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19])).toEqual([ + 1.2, 3.2, 5.7, 7.19, + ]); + }); +}); diff --git a/src/fp/array/uniqWith.ts b/src/fp/array/uniqWith.ts new file mode 100644 index 000000000..eb66df643 --- /dev/null +++ b/src/fp/array/uniqWith.ts @@ -0,0 +1,46 @@ +import { uniqWith as uniqWithToolkit } from '../../array/uniqWith'; + +/** + * Returns a new array containing only the unique elements from the original array, + * based on the values returned by the comparator function. + * + * @template T - The type of elements in the array. + * @param {T[]} arr - The array to process. + * @param {(item1: T, item2: T) => boolean} areItemsEqual - The function used to compare the array elements. + * @returns {T[]} A new array containing only the unique elements from the original array, based on the values returned by the comparator function. + * + * @example + * ```ts + * uniqWith([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], (a, b) => Math.abs(a - b) < 1); + * // [1.2, 3.2, 5.7, 7.19] + * ``` + */ +export function uniqWith(arr: readonly T[], areItemsEqual: (item1: T, item2: T) => boolean): T[]; + +/** + * Returns a new array containing only the unique elements from the original array, + * based on the values returned by the comparator function. + * + * @template T - The type of elements in the array. + * @param {(item1: T, item2: T) => boolean} areItemsEqual - The function used to compare the array elements. + * @returns {(arr: T[]) => T[]} A function that receive the array to process as argument and returns a new array containing only the unique elements from the original array, based on the values returned by the comparator function. + * + * @example + * ```ts + * uniqWith(1.5, 2.1, 3.2, 5.7, 5.3, 7.19], (a, b) => Math.abs(a - b) < 1)([1.2); + * // [1.2, 3.2, 5.7, 7.19] + * ``` + */ +export function uniqWith(areItemsEqual: (item1: T, item2: T) => boolean): (arr: readonly T[]) => T[]; + +export function uniqWith( + arrOrAreItemsEqual: readonly T[] | ((item1: T, item2: T) => boolean), + areItemsEqual?: (item1: T, item2: T) => boolean +) { + if (areItemsEqual == null) { + return (arr: readonly T[]) => uniqWith(arr, arrOrAreItemsEqual as (item1: T, item2: T) => boolean); + } + + const arr = arrOrAreItemsEqual as readonly T[]; + return uniqWithToolkit(arr, areItemsEqual); +} diff --git a/src/fp/array/unzipWith.spec.ts b/src/fp/array/unzipWith.spec.ts new file mode 100644 index 000000000..1be1b37b8 --- /dev/null +++ b/src/fp/array/unzipWith.spec.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from 'vitest'; +import { unzipWith } from './unzipWith'; +import { zip } from '../../array/zip'; + +describe('unzipWith', () => { + it("(non-curried) should unzip arrays correctly with an iteratee", () => { + const zipped = zip([10, 20, 30], [40, 50, 60], [70, 80, 90]); + const result = unzipWith(zipped, (item, item2, item3) => item + item2 + item3); + + expect(result).toEqual([60, 150, 240]); + }); + + it("(curried) should unzip arrays correctly with an iteratee", () => { + const zipped = zip([10, 20, 30], [40, 50, 60], [70, 80, 90]); + const result = unzipWith((item, item2, item3) => item + item2 + item3)(zipped); + + expect(result).toEqual([60, 150, 240]); + }); + + it("(non-curried) should handle arrays of different lengths", () => { + const zipped = zip([1, 20, 300, 4000], [100, 200, 300]); + const result = unzipWith(zipped, (item, item2, item3, item4) => item + item2 + item3 + item4); + + expect(result).toEqual([4321, NaN]); + }); + + it("(curried) should handle arrays of different lengths", () => { + const zipped = zip([1, 20, 300, 4000], [100, 200, 300]); + const result = unzipWith((item, item2, item3, item4) => item + item2 + item3 + item4)(zipped); + + expect(result).toEqual([4321, NaN]); + }); +}); diff --git a/src/fp/array/unzipWith.ts b/src/fp/array/unzipWith.ts new file mode 100644 index 000000000..33b7d96a1 --- /dev/null +++ b/src/fp/array/unzipWith.ts @@ -0,0 +1,44 @@ +import { unzipWith as unzipWithToolkit } from '../../array/unzipWith'; + +/** + * Unzips an array of arrays, applying an `iteratee` function to regrouped elements. + * + * @template T, R + * @param {T[][]} target - The nested array to unzip. This is an array of arrays, + * where each inner array contains elements to be unzipped. + * @param {(...args: T[]) => R} iteratee - A function to transform the unzipped elements. + * @returns {R[]} A new array of unzipped and transformed elements. + * + * @example + * const nestedArray = [[1, 2], [3, 4], [5, 6]]; + * const result = unzipWith(nestedArray, (item, item2, item3) => item + item2 + item3); + * // result will be [9, 12] + */ +export function unzipWith(target: readonly T[][], iteratee: (...args: T[]) => R): R[]; + +/** + * Unzips an array of arrays, applying an `iteratee` function to regrouped elements. + * + * @template T, R + * where each inner array contains elements to be unzipped. + * @param {(...args: T[]) => R} iteratee - A function to transform the unzipped elements. + * @returns {(target: T[][]) => R[]} A function that receive the nested array to unzip. This is an array of arrays, as argument and returns a new array of unzipped and transformed elements. + * + * @example + * const nestedArray = [[1, 2], [3, 4], [5, 6]]; + * const result = unzipWith((item, item2, item3) => item + item2 + item3)(nestedArray); + * // result will be [9, 12] + */ +export function unzipWith(iteratee: (...args: T[]) => R): (target: readonly T[][]) => R[]; + +export function unzipWith( + targetOrIteratee: readonly T[][] | ((...args: T[]) => R), + iteratee?: (...args: T[]) => R +) { + if (iteratee == null) { + return (target: readonly T[][]) => unzipWith(target, targetOrIteratee as (...args: T[]) => R); + } + + const target = targetOrIteratee as readonly T[][]; + return unzipWithToolkit(target, iteratee); +} diff --git a/src/fp/array/xor.spec.ts b/src/fp/array/xor.spec.ts new file mode 100644 index 000000000..4ce5df659 --- /dev/null +++ b/src/fp/array/xor.spec.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; +import { xor } from './xor'; + +describe('xor', () => { + it( + "(non-curried) computes the symmetric difference between two arrays", + () => { + expect(xor([1, 2, 3, 4], [3, 4, 5, 6])).toEqual([1, 2, 5, 6]); + expect(xor(['a', 'b'], ['b', 'c'])).toEqual(['a', 'c']); + expect(xor([1, 2, 3], [4, 5, 6])).toEqual([1, 2, 3, 4, 5, 6]); + expect(xor([1, 2, 3], [1, 2, 3])).toEqual([]); + expect(xor([], [1, 2, 3])).toEqual([1, 2, 3]); + expect(xor([1, 2, 3], [])).toEqual([1, 2, 3]); + } + ); + + it("(curried) computes the symmetric difference between two arrays", () => { + expect(xor([3, 4, 5, 6])([1, 2, 3, 4])).toEqual([1, 2, 5, 6]); + expect(xor(['b', 'c'])(['a', 'b'])).toEqual(['a', 'c']); + expect(xor([4, 5, 6])([1, 2, 3])).toEqual([1, 2, 3, 4, 5, 6]); + expect(xor([1, 2, 3])([1, 2, 3])).toEqual([]); + expect(xor([1, 2, 3])([])).toEqual([1, 2, 3]); + expect(xor([])([1, 2, 3])).toEqual([1, 2, 3]); + }); +}); diff --git a/src/fp/array/xor.ts b/src/fp/array/xor.ts new file mode 100644 index 000000000..2b7261a60 --- /dev/null +++ b/src/fp/array/xor.ts @@ -0,0 +1,50 @@ +import { difference } from './difference.ts'; +import { intersection } from './intersection.ts'; +import { union } from './union.ts'; +import { xor as xorToolkit } from '../../array/xor'; + +/** + * Computes the symmetric difference between two arrays. The symmetric difference is the set of elements + * which are in either of the arrays, but not in their intersection. + * + * @template T - The type of elements in the array. + * @param {T[]} arr1 - The first array. + * @param {T[]} arr2 - The second array. + * @returns {T[]} An array containing the elements that are present in either `arr1` or `arr2` but not in both. + * + * @example + * // Returns [1, 2, 5, 6] + * xor([1, 2, 3, 4], [3, 4, 5, 6]); + * + * @example + * // Returns ['a', 'c'] + * xor(['a', 'b'], ['b', 'c']); + */ +export function xor(arr1: readonly T[], arr2: readonly T[]): T[]; + +/** + * Computes the symmetric difference between two arrays. The symmetric difference is the set of elements + * which are in either of the arrays, but not in their intersection. + * + * @template T - The type of elements in the array. + * @param {T[]} arr2 - The second array. + * @returns {(arr1: T[]) => T[]} A function that receive the first array as argument and returns an array containing the elements that are present in either `arr1` or `arr2` but not in both. + * + * @example + * // Returns [1, 2, 5, 6] + * xor(2, 3, 4], [3, 4, 5, 6])([1); + * + * @example + * // Returns ['a', 'c'] + * xor(['a', 'b'], ['b', 'c']); + */ +export function xor(arr2: readonly T[]): (arr1: readonly T[]) => T[]; + +export function xor(arr1OrArr2: readonly T[] | readonly T[], arr2?: readonly T[]) { + if (arr2 == null) { + return (arr1: readonly T[]) => xor(arr1, arr1OrArr2 as readonly T[]); + } + + const arr1 = arr1OrArr2 as readonly T[]; + return xorToolkit(arr1, arr2); +} diff --git a/src/fp/array/xorBy.spec.ts b/src/fp/array/xorBy.spec.ts new file mode 100644 index 000000000..63d9fdeb0 --- /dev/null +++ b/src/fp/array/xorBy.spec.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; +import { xorBy } from './xorBy'; + +describe('xorBy', () => { + it('(non-curried) should compute symmetric difference using iteratee', () => { + const objects = [{ x: 1 }, { x: 2 }]; + const others = [{ x: 2 }, { x: 3 }]; + const result = xorBy(objects, others, value => value.x); + expect(result).toEqual([{ x: 1 }, { x: 3 }]); + }); + + it('(curried) should compute symmetric difference using iteratee', () => { + const objects = [{ x: 1 }, { x: 2 }]; + const others = [{ x: 2 }, { x: 3 }]; + const result = xorBy(others, value => value.x)(objects); + expect(result).toEqual([{ x: 1 }, { x: 3 }]); + }); + + it('(non-curried) should handle empty arrays', () => { + const result = xorBy([], [], (value: number) => value); + expect(result).toEqual([]); + }); + + it('(curried) should handle empty arrays', () => { + const result = xorBy([], (value: number) => value)([]); + expect(result).toEqual([]); + }); + + it('(non-curried) should not modify original arrays', () => { + const array1 = [{ x: 1 }, { x: 2 }]; + const array2 = [{ x: 2 }, { x: 3 }]; + const original1 = [...array1]; + const original2 = [...array2]; + xorBy(array1, array2, value => value.x); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); + + it('(curried) should not modify original arrays', () => { + const array1 = [{ x: 1 }, { x: 2 }]; + const array2 = [{ x: 2 }, { x: 3 }]; + const original1 = [...array1]; + const original2 = [...array2]; + xorBy(array2, value => value.x)(array1); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); +}); diff --git a/src/fp/array/xorBy.ts b/src/fp/array/xorBy.ts new file mode 100644 index 000000000..d4204ac0a --- /dev/null +++ b/src/fp/array/xorBy.ts @@ -0,0 +1,47 @@ +import { xorBy as xorByToolkit } from '../../array/xorBy'; + +/** + * Creates a function that computes the symmetric difference between two arrays + * using an iteratee function for comparison. + * + * @template T - The type of elements in the array. + * @template U - The type of the mapped elements. + * @param {Array} values - The values to compute the symmetric difference with. + * @param {(value: T) => U} iteratee - The function to transform elements for comparison. + * @returns {(arr: Array) => Array} A function that takes an array and returns a new array with the symmetric difference. + * + * @example + * const objects = [{ 'x': 1 }, { 'x': 2 }]; + * const xorByX = xorBy([{ 'x': 2 }, { 'x': 3 }], value => value.x); + * const result = xorByX(objects); + * // => [{ 'x': 1 }, { 'x': 3 }] + */ +export function xorBy(values: readonly T[], iteratee: (value: T) => U): (arr: readonly T[]) => T[]; + +/** + * Computes the symmetric difference between two arrays using an iteratee function for comparison. + * + * @template T - The type of elements in the array. + * @template U - The type of the mapped elements. + * @param {Array} arr - The array to inspect. + * @param {Array} values - The values to compute the symmetric difference with. + * @param {(value: T) => U} iteratee - The function to transform elements for comparison. + * @returns {Array} The new array of filtered values. + * + * @example + * xorBy([{ 'x': 1 }, { 'x': 2 }], [{ 'x': 2 }, { 'x': 3 }], value => value.x); + * // => [{ 'x': 1 }, { 'x': 3 }] + */ +export function xorBy(arr: readonly T[], values: readonly T[], iteratee: (value: T) => U): T[]; + +export function xorBy( + arrOrValues: readonly T[], + valuesOrIteratee: readonly T[] | ((value: T) => U), + iteratee?: (value: T) => U +) { + if (typeof valuesOrIteratee === 'function') { + return (arr: readonly T[]) => xorBy(arr, arrOrValues, valuesOrIteratee); + } + + return xorByToolkit(arrOrValues, valuesOrIteratee, iteratee!); +} diff --git a/src/fp/array/xorWith.spec.ts b/src/fp/array/xorWith.spec.ts new file mode 100644 index 000000000..cee0f8e0b --- /dev/null +++ b/src/fp/array/xorWith.spec.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest'; +import { xorWith } from './xorWith'; + +describe('xorWith', () => { + const isEqual = (a: { x: number; y: number }, b: { x: number; y: number }) => + a.x === b.x && a.y === b.y; + + it('(non-curried) should compute symmetric difference using comparator', () => { + const objects = [{ x: 1, y: 2 }, { x: 2, y: 1 }]; + const others = [{ x: 1, y: 1 }, { x: 1, y: 2 }]; + const result = xorWith(objects, others, isEqual); + expect(result).toEqual([ + { x: 2, y: 1 }, + { x: 1, y: 1 }, + ]); + }); + + it('(curried) should compute symmetric difference using comparator', () => { + const objects = [{ x: 1, y: 2 }, { x: 2, y: 1 }]; + const others = [{ x: 1, y: 1 }, { x: 1, y: 2 }]; + const result = xorWith(others, isEqual)(objects); + expect(result).toEqual([ + { x: 2, y: 1 }, + { x: 1, y: 1 }, + ]); + }); + + it('(non-curried) should handle empty arrays', () => { + const result = xorWith([], [], isEqual); + expect(result).toEqual([]); + }); + + it('(curried) should handle empty arrays', () => { + const result = xorWith([], isEqual)([]); + expect(result).toEqual([]); + }); + + it('(non-curried) should not modify original arrays', () => { + const array1 = [{ x: 1, y: 2 }]; + const array2 = [{ x: 1, y: 1 }]; + const original1 = [...array1]; + const original2 = [...array2]; + xorWith(array1, array2, isEqual); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); + + it('(curried) should not modify original arrays', () => { + const array1 = [{ x: 1, y: 2 }]; + const array2 = [{ x: 1, y: 1 }]; + const original1 = [...array1]; + const original2 = [...array2]; + xorWith(array2, isEqual)(array1); + expect(array1).toEqual(original1); + expect(array2).toEqual(original2); + }); +}); diff --git a/src/fp/array/xorWith.ts b/src/fp/array/xorWith.ts new file mode 100644 index 000000000..fb4b92ca6 --- /dev/null +++ b/src/fp/array/xorWith.ts @@ -0,0 +1,48 @@ +import { xorWith as xorWithToolkit } from '../../array/xorWith'; + +/** + * Creates a function that computes the symmetric difference between two arrays + * using a comparator function. + * + * @template T - The type of elements in the array. + * @param {Array} values - The values to compute the symmetric difference with. + * @param {(a: T, b: T) => boolean} comparator - The function to compare elements. + * @returns {(arr: Array) => Array} A function that takes an array and returns a new array with the symmetric difference. + * + * @example + * const objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * const others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * const isEqual = (a, b) => a.x === b.x && a.y === b.y; + * const xorWithEqual = xorWith(others, isEqual); + * const result = xorWithEqual(objects); + * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] + */ +export function xorWith(values: readonly T[], comparator: (a: T, b: T) => boolean): (arr: readonly T[]) => T[]; + +/** + * Computes the symmetric difference between two arrays using a comparator function. + * + * @template T - The type of elements in the array. + * @param {Array} arr - The array to inspect. + * @param {Array} values - The values to compute the symmetric difference with. + * @param {(a: T, b: T) => boolean} comparator - The function to compare elements. + * @returns {Array} The new array of filtered values. + * + * @example + * const isEqual = (a, b) => a.x === b.x && a.y === b.y; + * xorWith([{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }], [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }], isEqual); + * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] + */ +export function xorWith(arr: readonly T[], values: readonly T[], comparator: (a: T, b: T) => boolean): T[]; + +export function xorWith( + arrOrValues: readonly T[], + valuesOrComparator: readonly T[] | ((a: T, b: T) => boolean), + comparator?: (a: T, b: T) => boolean +) { + if (typeof valuesOrComparator === 'function') { + return (arr: readonly T[]) => xorWith(arr, arrOrValues, valuesOrComparator); + } + + return xorWithToolkit(arrOrValues, valuesOrComparator, comparator!); +} diff --git a/src/fp/array/zipObject.spec.ts b/src/fp/array/zipObject.spec.ts new file mode 100644 index 000000000..23609b2db --- /dev/null +++ b/src/fp/array/zipObject.spec.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest'; +import { zipObject } from './zipObject'; + +describe('zipObject', () => { + it( + "(non-curried) creates an object from two arrays of keys and values", + () => { + expect(zipObject(['a', 'b', 'c'], [1, 2, 3])).toEqual({ a: 1, b: 2, c: 3 }); + + expect(zipObject(['a', 'b', 'c'], [1, 2])).toEqual({ a: 1, b: 2, c: undefined }); + + expect(zipObject(['a', 'b'], [1, 2, 3])).toEqual({ a: 1, b: 2 }); + } + ); + + it("(curried) creates an object from two arrays of keys and values", () => { + expect(zipObject([1, 2, 3])(['a', 'b', 'c'])).toEqual({ a: 1, b: 2, c: 3 }); + + expect(zipObject([1, 2])(['a', 'b', 'c'])).toEqual({ a: 1, b: 2, c: undefined }); + + expect(zipObject([1, 2, 3])(['a', 'b'])).toEqual({ a: 1, b: 2 }); + }); +}); diff --git a/src/fp/array/zipObject.ts b/src/fp/array/zipObject.ts new file mode 100644 index 000000000..be113447d --- /dev/null +++ b/src/fp/array/zipObject.ts @@ -0,0 +1,73 @@ +import { zipObject as zipObjectToolkit } from '../../array/zipObject'; + +/** + * Combines two arrays, one of property names and one of corresponding values, into a single object. + * + * This function takes two arrays: one containing property names and another containing corresponding values. + * It returns a new object where the property names from the first array are keys, and the corresponding elements + * from the second array are values. If the `keys` array is longer than the `values` array, the remaining keys will + * have `undefined` as their values. + * + * @template P - The type of elements in the array. + * @template V - The type of elements in the array. + * @param {P[]} keys - An array of property names. + * @param {V[]} values - An array of values corresponding to the property names. + * @returns {Record} - A new object composed of the given property names and values. + * + * @example + * const keys = ['a', 'b', 'c']; + * const values = [1, 2, 3]; + * const result = zipObject(keys, values); + * // result will be { a: 1, b: 2, c: 3 } + * + * const keys2 = ['a', 'b', 'c']; + * const values2 = [1, 2]; + * const result2 = zipObject(keys2, values2); + * // result2 will be { a: 1, b: 2, c: undefined } + * + * const keys2 = ['a', 'b']; + * const values2 = [1, 2, 3]; + * const result2 = zipObject(keys2, values2); + * // result2 will be { a: 1, b: 2 } + */ +export function zipObject

(keys: readonly P[], values: readonly V[]): Record; + +/** + * Combines two arrays, one of property names and one of corresponding values, into a single object. + * + * This function takes two arrays: one containing property names and another containing corresponding values. + * It returns a new object where the property names from the first array are keys, and the corresponding elements + * from the second array are values. If the `keys` array is longer than the `values` array, the remaining keys will + * have `undefined` as their values. + * + * @template P - The type of elements in the array. + * @template V - The type of elements in the array. + * @param {V[]} values - An array of values corresponding to the property names. + * @returns {(keys: P[]) => Record} A function that receive an array of property names as argument and returns - A new object composed of the given property names and values. + * + * @example + * const keys = ['a', 'b', 'c']; + * const values = [1, 2, 3]; + * const result = zipObject(values)(keys); + * // result will be { a: 1, b: 2, c: 3 } + * + * const keys2 = ['a', 'b', 'c']; + * const values2 = [1, 2]; + * const result2 = zipObject(keys2, values2); + * // result2 will be { a: 1, b: 2, c: undefined } + * + * const keys2 = ['a', 'b']; + * const values2 = [1, 2, 3]; + * const result2 = zipObject(keys2, values2); + * // result2 will be { a: 1, b: 2 } + */ +export function zipObject

(values: readonly V[]): (keys: readonly P[]) => Record; + +export function zipObject

(keysOrValues: readonly P[] | readonly V[], values?: readonly V[]) { + if (values == null) { + return (keys: readonly P[]) => zipObject(keys, keysOrValues as readonly V[]); + } + + const keys = keysOrValues as readonly P[]; + return zipObjectToolkit(keys, values); +} diff --git a/src/fp/core/pipe.spec.ts b/src/fp/core/pipe.spec.ts new file mode 100644 index 000000000..59de55f94 --- /dev/null +++ b/src/fp/core/pipe.spec.ts @@ -0,0 +1,157 @@ +import { describe, expect, expectTypeOf, it } from 'vitest'; +import { pipe } from './pipe'; + +describe('pipe', () => { + const isString = (value: unknown): value is string => typeof value === 'string'; + const toString = (value: unknown) => `string:${value}`; + const toStringAsync = (value: unknown) => + new Promise(resolve => setTimeout(() => resolve(`string:${value}`), 100)); + const length = (value: string) => value.length; + + it('should return value of the last function should be returned', () => { + expect(pipe(1, toString)).toBe('string:1'); + expect(pipe(true, toString)).toBe('string:true'); + expect(pipe(true, toString, isString)).toBe(true); + + expect(pipe(1, toString, length)).toBe(8); + expect(pipe(true, toString, length)).toBe(11); + expect(pipe(true, toString, length, isString)).toBe(false); + }); + + it('should return promise value if there is a function that returns a promise in the middle', async () => { + const promiseString = pipe(1, toStringAsync); + expect(promiseString).toBeInstanceOf(Promise); + expect(await promiseString).toBe('string:1'); + + const promiseLength = pipe(1, toStringAsync, length); + expect(promiseLength).toBeInstanceOf(Promise); + expect(await promiseLength).toBe(8); + + expectTypeOf(pipe(promiseString, isString)).toMatchTypeOf>(); + expectTypeOf(pipe(promiseLength, isString)).toMatchTypeOf>(); + }); + + it('should not change original value', () => { + const curriedMap = (mapper: (value: T) => R) => { + return (arr: T[]) => { + for (let i = 0; i < arr.length; i++) { + (arr as any)[i] = mapper(arr[i]); + } + + return arr; + }; + }; + + const curriedMapValue = , R>(mapper: (value: T[keyof T]) => R) => { + return (obj: T) => { + for (const key of Object.keys(obj)) { + (obj as any)[key] = mapper(obj[key] as any); + } + + return obj; + }; + }; + + const originArr = [1, 2, 3]; + + pipe(originArr, curriedMap(toString)); + + expect(originArr).toEqual([1, 2, 3]); + + const originObj = { a: 1, b: 2, c: 3 }; + + pipe( + originObj, + curriedMapValue(value => value + 5) + ); + + expect(originObj).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it('should inference type correctly', () => { + expectTypeOf(pipe(1, toString)).toEqualTypeOf(); + expectTypeOf(pipe(1, toString, isString)).toEqualTypeOf(); + expectTypeOf(pipe(1, toString, length)).toEqualTypeOf(); + expectTypeOf(pipe(1, toString, length, isString)).toEqualTypeOf(); + expectTypeOf(pipe(1, toStringAsync)).toEqualTypeOf>(); + expectTypeOf(pipe(1, toStringAsync, length)).toEqualTypeOf>(); + expectTypeOf(pipe(1, toStringAsync, isString)).toMatchTypeOf>(); + expectTypeOf(pipe(1, toStringAsync, length, isString)).toMatchTypeOf>(); + }); +}); + +describe('pipe.lazy', () => { + const isString = (value: unknown): value is string => typeof value === 'string'; + const toString = (value: unknown) => `string:${value}`; + const toStringAsync = (value: unknown) => + new Promise(resolve => setTimeout(() => resolve(`string:${value}`), 100)); + const length = (value: string) => value.length; + + it('should return value of the last function should be returned', () => { + expect(pipe.lazy(toString)(1)).toBe('string:1'); + expect(pipe.lazy(toString)(true)).toBe('string:true'); + expect(pipe.lazy(toString, isString)(true)).toBe(true); + + expect(pipe.lazy(toString, length)(1)).toBe(8); + expect(pipe.lazy(toString, length)(true)).toBe(11); + expect(pipe.lazy(toString, length, isString)(true)).toBe(false); + }); + + it('should return promise value if there is a function that returns a promise in the middle', async () => { + const promiseString = pipe.lazy(toStringAsync)(1); + expect(promiseString).toBeInstanceOf(Promise); + expect(await promiseString).toBe('string:1'); + + const promiseLength = pipe.lazy(toStringAsync, length)(1); + expect(promiseLength).toBeInstanceOf(Promise); + expect(await promiseLength).toBe(8); + + expectTypeOf(pipe.lazy(isString)(promiseString)).toMatchTypeOf>(); + expectTypeOf(pipe.lazy(isString)(promiseLength)).toMatchTypeOf>(); + }); + + it('should not change original value', () => { + const curriedMap = (mapper: (value: T) => R) => { + return (arr: T[]) => { + for (let i = 0; i < arr.length; i++) { + (arr as any)[i] = mapper(arr[i]); + } + + return arr; + }; + }; + + const curriedMapValue = , R>(mapper: (value: T[string]) => R) => { + return (obj: T) => { + for (const key of Object.keys(obj)) { + (obj as any)[key] = mapper(obj[key]); + } + + return obj; + }; + }; + + const originArr = [1, 2, 3]; + + pipe.lazy(curriedMap(toString))(originArr); + + expect(originArr).toEqual([1, 2, 3]); + + const originObj = { a: 1, b: 2, c: 3 }; + + pipe.lazy(curriedMapValue(value => value + 5))(originObj); + + expect(originObj).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it('should inference type correctly', () => { + expectTypeOf(pipe.lazy(toString)(1)).toEqualTypeOf(); + expectTypeOf(pipe.lazy(toString, isString)(1)).toEqualTypeOf(); + expectTypeOf(pipe.lazy(toString, length)(1)).toEqualTypeOf(); + expectTypeOf(pipe.lazy(toString, length, isString)(1)).toEqualTypeOf(); + expectTypeOf(pipe.lazy(toStringAsync)(1)).toEqualTypeOf>(); + expectTypeOf(pipe.lazy(toStringAsync, length)(1)).toEqualTypeOf>(); + expectTypeOf(pipe.lazy(toStringAsync, isString)(1)).toMatchTypeOf>(); + expectTypeOf(pipe.lazy(toStringAsync, length, isString)(1)).toMatchTypeOf>(); + }); +}); diff --git a/src/fp/core/pipe.ts b/src/fp/core/pipe.ts new file mode 100644 index 000000000..cf6c936ba --- /dev/null +++ b/src/fp/core/pipe.ts @@ -0,0 +1,2163 @@ +import { cloneDeep } from '../../object'; + +type Parameter = T extends Promise ? P : T; + +type PipeReturnType = T extends [infer First, ...infer Last] + ? Promised extends true + ? PipeReturnType + : First extends Promise + ? PipeReturnType + : PipeReturnType + : Last extends Promise + ? Last + : Promised extends true + ? Promise + : Last; + +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @returns {PipeReturnType<[T1, T2]>} A processed value - return value of 1st function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * + * const result = pipe(1, toString); + * console.log(result); // 'string:1' + * + * // Use pipe with async function + * const asyncResult = await pipe(1, toStringAsync); + * console.log(asyncResult); // 'string:1' + * + * // Use pipe with curried function + * const mapKeyResult = await pipe( + * { a: 1, b: 2 }, + * mapKeys((value, key) => key + value) + * ); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe(initial: T1, fn1: (arg: Parameter) => T2): PipeReturnType<[T1, T2]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3]>} A processed value - return value of 2nd function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3 +): PipeReturnType<[T1, T2, T3]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4]>} A processed value - return value of 3rd function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4 +): PipeReturnType<[T1, T2, T3, T4]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5]>} A processed value - return value of 4th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5 +): PipeReturnType<[T1, T2, T3, T4, T5]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6]>} A processed value - return value of 5th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6 +): PipeReturnType<[T1, T2, T3, T4, T5, T6]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7]>} A processed value - return value of 6th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8]>} A processed value - return value of 7th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>} A processed value - return value of 8th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>} A processed value - return value of 9th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]>} A processed value - return value of 10th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]>} A processed value - return value of 11th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]>} A processed value - return value of 12th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]>} A processed value - return value of 13th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]>} A processed value - return value of 14th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]>} A processed value - return value of 15th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @param {(arg: Parameter) => T17} fn16 - 16th function that receives return value of 15th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]>} A processed value - return value of 16th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16, + fn16: (arg: Parameter) => T17 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @param {(arg: Parameter) => T17} fn16 - 16th function that receives return value of 15th function as its parameter. + * @param {(arg: Parameter) => T18} fn17 - 17th function that receives return value of 16th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]>} A processed value - return value of 17th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16, + fn16: (arg: Parameter) => T17, + fn17: (arg: Parameter) => T18 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @param {(arg: Parameter) => T17} fn16 - 16th function that receives return value of 15th function as its parameter. + * @param {(arg: Parameter) => T18} fn17 - 17th function that receives return value of 16th function as its parameter. + * @param {(arg: Parameter) => T19} fn18 - 18th function that receives return value of 17th function as its parameter. + * @returns {PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]>} A processed value - return value of 18th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +export function pipe( + initial: T1, + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16, + fn16: (arg: Parameter) => T17, + fn17: (arg: Parameter) => T18, + fn18: (arg: Parameter) => T19 +): PipeReturnType<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]>; + +export function pipe(initial: any, ...functions: Array<(arg: any) => any>): any { + const cloned = cloneDeep(initial); + return getNextFunction(cloned, functions); +} + +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2]>} A processed value - return value of 1st function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * + * const result = pipe.lazy(toString)(1); + * console.log(result); // 'string:1' + * + * // Use pipe with async function + * const pipedToStringAsync = pipe.lazy(toStringAsync); + * const asyncResult = await pipedToStringAsync(1); + * console.log(asyncResult); // 'string:1' + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2 +): >(initial: I) => PipeReturnType<[I, T2]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3]>} A processed value - return value of 2nd function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3 +): >(initial: I) => PipeReturnType<[I, T2, T3]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4]>} A processed value - return value of 3rd function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5]>} A processed value - return value of 4th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6]>} A processed value - return value of 5th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7]>} A processed value - return value of 6th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8]>} A processed value - return value of 7th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9]>} A processed value - return value of 8th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10]>} A processed value - return value of 9th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]>} A processed value - return value of 10th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]>} A processed value - return value of 11th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]>} A processed value - return value of 12th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13 +): >(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]>} A processed value - return value of 13th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14 +): >( + initial: I +) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]>} A processed value - return value of 14th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15 +): >( + initial: I +) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]>} A processed value - return value of 15th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16 +): >( + initial: I +) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @param {(arg: Parameter) => T17} fn16 - 16th function that receives return value of 15th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]>} A processed value - return value of 16th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16, + fn16: (arg: Parameter) => T17 +): >( + initial: I +) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @param {(arg: Parameter) => T17} fn16 - 16th function that receives return value of 15th function as its parameter. + * @param {(arg: Parameter) => T18} fn17 - 17th function that receives return value of 16th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]>} A processed value - return value of 17th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16, + fn16: (arg: Parameter) => T17, + fn17: (arg: Parameter) => T18 +): >( + initial: I +) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]>; +/** + * Processes the value as it passes through pipe. It is useful for declaratively writing code that transforms a value through multiple stages. + * + * When using `pipe.lazy`, do not pass the initial value as first argument of 'first function'. + * "pipe.lazy implements lazy execution by calling a function twice. + * Provide the functions that process the value in order when calling the first function, + * and pass the initial value to the function when calling the second function. + * + * @param {T1} initial - The initial value to be processed. + * @param {(arg: Parameter) => T2} fn1 - 1st function that receives initial value as its parameter. + * @param {(arg: Parameter) => T3} fn2 - 2nd function that receives return value of 1st function as its parameter. + * @param {(arg: Parameter) => T4} fn3 - 3rd function that receives return value of 2nd function as its parameter. + * @param {(arg: Parameter) => T5} fn4 - 4th function that receives return value of 3rd function as its parameter. + * @param {(arg: Parameter) => T6} fn5 - 5th function that receives return value of 4th function as its parameter. + * @param {(arg: Parameter) => T7} fn6 - 6th function that receives return value of 5th function as its parameter. + * @param {(arg: Parameter) => T8} fn7 - 7th function that receives return value of 6th function as its parameter. + * @param {(arg: Parameter) => T9} fn8 - 8th function that receives return value of 7th function as its parameter. + * @param {(arg: Parameter) => T10} fn9 - 9th function that receives return value of 8th function as its parameter. + * @param {(arg: Parameter) => T11} fn10 - 10th function that receives return value of 9th function as its parameter. + * @param {(arg: Parameter) => T12} fn11 - 11th function that receives return value of 10th function as its parameter. + * @param {(arg: Parameter) => T13} fn12 - 12th function that receives return value of 11th function as its parameter. + * @param {(arg: Parameter) => T14} fn13 - 13th function that receives return value of 12th function as its parameter. + * @param {(arg: Parameter) => T15} fn14 - 14th function that receives return value of 13th function as its parameter. + * @param {(arg: Parameter) => T16} fn15 - 15th function that receives return value of 14th function as its parameter. + * @param {(arg: Parameter) => T17} fn16 - 16th function that receives return value of 15th function as its parameter. + * @param {(arg: Parameter) => T18} fn17 - 17th function that receives return value of 16th function as its parameter. + * @param {(arg: Parameter) => T19} fn18 - 18th function that receives return value of 17th function as its parameter. + * @returns {(initial: I) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]>} A processed value - return value of 18th function. + * + * @example + * function toString(value: unknown) { + * return `string:${value}`; + * } + * function toStringAsync(value: unknown) { + * return Promise.resolve(`string:${value}`); + * } + * function length(value: string) { + * return value.length; + * } + * + * const result = pipe(toString, length)(1); + * console.log(result); // 8 + + * // Use pipe with async function + * const pipedLength = pipe(toStringAsync, length); + * const asyncResult = await pipedLength(1); + * console.log(asyncResult); // 8 + * + * // Use pipe with curried function + * const pipedMapKeys = pipe.lazy( + * mapKeys((value, key) => key + value) + * ); + * const mapKeyResult = await pipedMapKeys({ a: 1, b: 2 }); + * console.log(mapKeyResult); // { a1: 1, b2: 2 } + */ +function pipeLazy( + fn1: (arg: Parameter) => T2, + fn2: (arg: Parameter) => T3, + fn3: (arg: Parameter) => T4, + fn4: (arg: Parameter) => T5, + fn5: (arg: Parameter) => T6, + fn6: (arg: Parameter) => T7, + fn7: (arg: Parameter) => T8, + fn8: (arg: Parameter) => T9, + fn9: (arg: Parameter) => T10, + fn10: (arg: Parameter) => T11, + fn11: (arg: Parameter) => T12, + fn12: (arg: Parameter) => T13, + fn13: (arg: Parameter) => T14, + fn14: (arg: Parameter) => T15, + fn15: (arg: Parameter) => T16, + fn16: (arg: Parameter) => T17, + fn17: (arg: Parameter) => T18, + fn18: (arg: Parameter) => T19 +): >( + initial: I +) => PipeReturnType<[I, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]>; + +function pipeLazy(...functions: Array<(arg: any) => any>) { + return (initial: any) => { + const cloned = cloneDeep(initial); + return getNextFunction(cloned, functions); + }; +} + +pipe.lazy = pipeLazy; + +function getNextFunction(prop: any, functions: Array<(arg: any) => any>) { + if (functions.length === 0) { + return prop; + } + + const [currentFunction, ...nextFunctions] = functions; + + if (prop instanceof Promise) { + prop.then(value => chainToNextFunction(value, currentFunction, nextFunctions)); + } + + return chainToNextFunction(prop, currentFunction, nextFunctions); +} + +function chainToNextFunction( + prop: any, + currentFunction: (arg: any) => any, + nextFunctions: Array<(arg: any) => any> +): any { + const returnValue = currentFunction(prop); + + if (returnValue instanceof Promise) { + return returnValue.then((value: any): any => getNextFunction(value, nextFunctions)); + } else { + return getNextFunction(returnValue, nextFunctions); + } +} diff --git a/src/fp/index.ts b/src/fp/index.ts new file mode 100644 index 000000000..d8255fd2e --- /dev/null +++ b/src/fp/index.ts @@ -0,0 +1,6 @@ +export * from '../index.ts'; + +export { pipe } from './core/pipe'; +export { mapKeys } from './object/mapKeys'; +export { omit } from './object/omit'; +export { omitBy } from './object/omitBy'; \ No newline at end of file diff --git a/src/fp/object/mapKeys.spec.ts b/src/fp/object/mapKeys.spec.ts new file mode 100644 index 000000000..5b02dd35b --- /dev/null +++ b/src/fp/object/mapKeys.spec.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import { mapKeys } from './mapKeys'; + +describe('mapKeys', () => { + it('(non-curried) should iterate over and map the object using its own string keys', () => { + expect(mapKeys({ a: 1, b: 2, c: 3 }, (_, key) => `${key}a`)).toEqual({ aa: 1, ba: 2, ca: 3 }); + }); + + it('(curried) should iterate over and map the object using its own string keys', () => { + expect(mapKeys>((_, key) => `${key}a`)({ a: 1, b: 2, c: 3 })).toEqual({ + aa: 1, + ba: 2, + ca: 3, + }); + }); + + it('(non-curried) should iterate over and map the object using its own number keys', () => { + expect(mapKeys({ 1: 'a', 2: 'b', 3: 'c' }, (_, key) => key * 2)).toEqual({ 2: 'a', 4: 'b', 6: 'c' }); + }); + + it('(curried) should iterate over and map the object using its own number keys', () => { + expect(mapKeys>((_, key) => key * 2)({ 1: 'a', 2: 'b', 3: 'c' })).toEqual({ + 2: 'a', + 4: 'b', + 6: 'c', + }); + }); + + it('(non-curried) should pass the value corresponding to the current key into the iteratee', () => { + expect(mapKeys({ a: 1, b: 2, c: 3 }, value => value)).toEqual({ 1: 1, 2: 2, 3: 3 }); + }); + + it('(curried) should pass the value corresponding to the current key into the iteratee', () => { + expect(mapKeys>(value => value)({ a: 1, b: 2, c: 3 })).toEqual({ 1: 1, 2: 2, 3: 3 }); + }); +}); diff --git a/src/fp/object/mapKeys.ts b/src/fp/object/mapKeys.ts new file mode 100644 index 000000000..85df134b8 --- /dev/null +++ b/src/fp/object/mapKeys.ts @@ -0,0 +1,57 @@ +import { mapKeys as mapKeysToolkit } from '../../object/mapKeys'; + +/** + * Creates a new object with the same values as the given object, but with keys generated + * by running each own enumerable property of the object through the iteratee function. + * + * @template T - The type of the object. + * @template K1 - The type of the keys in the object. + * @template K2 - The type of the new keys generated by the iteratee function. + * + * @param {T} object - The object to iterate over. + * @param {(value: T[K1], key: K1, object: T) => K2} getNewKey - The function invoked per own enumerable property. + * @returns {Record} - Returns the new mapped object. + * + * @example + * // Example usage: + * const obj = { a: 1, b: 2 }; + * const result = mapKeys(obj, (value, key) => key + value); + * console.log(result); // { a1: 1, b2: 2 } + */ +export function mapKeys( + object: T, + getNewKey: (value: T[K1], key: K1, object: T) => K2 +): Record; + +/** + * Creates a new object with the same values as the given object, but with keys generated + * by running each own enumerable property of the object through the iteratee function. + * + * @template T - The type of the object. + * @template K1 - The type of the keys in the object. + * @template K2 - The type of the new keys generated by the iteratee function. + * + * @param {(value: T[K1], key: K1, object: T) => K2} getNewKey - The function invoked per own enumerable property. + * @returns {(object: T) => Record} A function that receive the object to iterate over as argument and returns - Returns the new mapped object. + * + * @example + * // Example usage: + * const obj = { a: 1, b: 2 }; + * const result = mapKeys((value, key) => key + value)(obj); + * console.log(result); // { a1: 1, b2: 2 } + */ +export function mapKeys( + getNewKey: (value: T[K1], key: K1, object: T) => K2 +): (object: T) => Record; + +export function mapKeys( + objectOrGetNewKey: T | ((value: T[K1], key: K1, object: T) => K2), + getNewKey?: (value: T[K1], key: K1, object: T) => K2 +) { + if (getNewKey == null) { + return (object: T) => mapKeys(object, objectOrGetNewKey as (value: T[K1], key: K1, object: T) => K2); + } + + const object = objectOrGetNewKey as T; + return mapKeysToolkit(object, getNewKey as any); +} diff --git a/src/fp/object/mapValues.spec.ts b/src/fp/object/mapValues.spec.ts new file mode 100644 index 000000000..7330e3466 --- /dev/null +++ b/src/fp/object/mapValues.spec.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; +import { mapValues } from './mapValues'; + +describe('mapValues', () => { + it( + "(non-curried) should iterate over and map the object using its own values", + () => { + expect(mapValues({ a: 1, b: 2, c: 3 }, value => ++value)).toEqual({ a: 2, b: 3, c: 4 }); + } + ); + + it("(curried) should iterate over and map the object using its own values", () => { + expect(mapValues(value => ++value)({ a: 1, b: 2, c: 3 })).toEqual({ a: 2, b: 3, c: 4 }); + }); + + it( + "(non-curried) should pass the key corresponding to the current value into the iteratee", + () => { + expect(mapValues({ a: 1, b: 2, c: 3 }, (_, key) => key)).toEqual({ a: 'a', b: 'b', c: 'c' }); + } + ); + + it("(curried) should pass the key corresponding to the current value into the iteratee", () => { + expect(mapValues((_, key) => key)({ a: 1, b: 2, c: 3 })).toEqual({ a: 'a', b: 'b', c: 'c' }); + }); + + it("(non-curried) should pass the cloned object into the iteratee", () => { + expect( + mapValues({ a: 1, b: 2, c: 3 }, (value, key, object) => { + object[key] = value * 11; + return value * 11; + }) + ).toEqual({ a: 11, b: 22, c: 33 }); + }); + + it("(curried) should pass the cloned object into the iteratee", () => { + expect( + mapValues<{ a: number; b: number; c: number }, 'a' | 'b' | 'c', number>( + (value, key, object) => { + object[key] = value * 11; + return value * 11; + } + )({ a: 1, b: 2, c: 3 }) + ).toEqual({ a: 11, b: 22, c: 33 }); + }); +}); diff --git a/src/fp/object/mapValues.ts b/src/fp/object/mapValues.ts new file mode 100644 index 000000000..78ecd19b2 --- /dev/null +++ b/src/fp/object/mapValues.ts @@ -0,0 +1,57 @@ +import { mapValues as mapValuesToolkit } from '../../object/mapValues'; + +/** + * Creates a new object with the same keys as the given object, but with values generated + * by running each own enumerable property of the object through the iteratee function. + * + * @template T - The type of the object. + * @template K - The type of the keys in the object. + * @template V - The type of the new values generated by the iteratee function. + * + * @param {T} object - The object to iterate over. + * @param {(value: T[K], key: K, object: T) => V} getNewValue - The function invoked per own enumerable property. + * @returns {Record} - Returns the new mapped object. + * + * @example + * // Example usage: + * const obj = { a: 1, b: 2 }; + * const result = mapValues(obj, (value) => value * 2); + * console.log(result); // { a: 2, b: 4 } + */ +export function mapValues( + object: T, + getNewValue: (value: T[K], key: K, object: T) => V +): Record; + +/** + * Creates a new object with the same keys as the given object, but with values generated + * by running each own enumerable property of the object through the iteratee function. + * + * @template T - The type of the object. + * @template K - The type of the keys in the object. + * @template V - The type of the new values generated by the iteratee function. + * + * @param {(value: T[K], key: K, object: T) => V} getNewValue - The function invoked per own enumerable property. + * @returns {(object: T) => Record} A function that receive the object to iterate over as argument and returns - Returns the new mapped object. + * + * @example + * // Example usage: + * const obj = { a: 1, b: 2 }; + * const result = mapValues((value) => value * 2)(obj); + * console.log(result); // { a: 2, b: 4 } + */ +export function mapValues( + getNewValue: (value: T[K], key: K, object: T) => V +): (object: T) => Record; + +export function mapValues( + objectOrGetNewValue: T | ((value: T[K], key: K, object: T) => V), + getNewValue?: (value: T[K], key: K, object: T) => V +) { + if (getNewValue == null) { + return (object: T) => mapValues(object, objectOrGetNewValue as (value: T[K], key: K, object: T) => V); + } + + const object = objectOrGetNewValue as T; + return mapValuesToolkit(object, getNewValue); +} diff --git a/src/fp/object/merge.spec.ts b/src/fp/object/merge.spec.ts new file mode 100644 index 000000000..a5f7252c8 --- /dev/null +++ b/src/fp/object/merge.spec.ts @@ -0,0 +1,231 @@ +import { describe, expect, it } from 'vitest'; +import { merge } from './merge'; + +describe('merge', () => { + it( + "(non-curried) should merge properties from source object into target object", + () => { + const target = { a: 1, b: 2 }; + const source = { b: 3, c: 4 }; + const result = merge(target, source); + + expect(result).toEqual({ a: 1, b: 3, c: 4 }); + } + ); + + it("(curried) should merge properties from source object into target object", () => { + const target = { a: 1, b: 2 }; + const source = { b: 3, c: 4 }; + const result = merge(source)(target); + + expect(result).toEqual({ a: 1, b: 3, c: 4 }); + }); + + it("(non-curried) should deeply merge nested objects", () => { + const target = { a: { x: 1, y: 2 }, b: 2 }; + const source = { a: { y: 3, z: 4 }, c: 5 }; + const result = merge(target, source); + + expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 2, c: 5 }); + + const names = { + characters: [{ name: 'barney' }, { name: 'fred' }], + }; + + const ages = { + characters: [{ age: 36 }, { age: 40 }], + }; + + const heights = { + characters: [{ height: '5\'4"' }, { height: '5\'5"' }], + }; + + const expected = { + characters: [ + { name: 'barney', age: 36, height: '5\'4"' }, + { name: 'fred', age: 40, height: '5\'5"' }, + ], + }; + + expect(merge(merge(names, ages), heights)).toEqual(expected); + + const target2 = { a: [1, 2], b: { x: 1 } }; + const source2 = { a: [3], b: { y: 2 } }; + expect(merge(target2, source2)).toEqual({ a: [3, 2], b: { x: 1, y: 2 } }); + }); + + it("(curried) should deeply merge nested objects", () => { + const target = { a: { x: 1, y: 2 }, b: 2 }; + const source = { a: { y: 3, z: 4 }, c: 5 }; + const result = merge(source)(target); + + expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 2, c: 5 }); + + const names = { + characters: [{ name: 'barney' }, { name: 'fred' }], + }; + + const ages = { + characters: [{ age: 36 }, { age: 40 }], + }; + + const heights = { + characters: [{ height: '5\'4"' }, { height: '5\'5"' }], + }; + + const expected = { + characters: [ + { name: 'barney', age: 36, height: '5\'4"' }, + { name: 'fred', age: 40, height: '5\'5"' }, + ], + }; + + expect(merge(heights)(merge(ages)(names))).toEqual(expected); + + const target2 = { a: [1, 2], b: { x: 1 } }; + const source2 = { a: [3], b: { y: 2 } }; + expect(merge(source2)(target2)).toEqual({ a: [3, 2], b: { x: 1, y: 2 } }); + }); + + it("(non-curried) should merge arrays deeply", () => { + const target = { a: [1, 2] }; + const source = { a: [3, 4] }; + const result = merge(target, source); + + expect(result).toEqual({ a: [3, 4] }); + }); + + it("(curried) should merge arrays deeply", () => { + const target = { a: [1, 2] }; + const source = { a: [3, 4] }; + const result = merge(source)(target); + + expect(result).toEqual({ a: [3, 4] }); + }); + + it("(non-curried) should handle merging with null values", () => { + const target = { a: null }; + const source = { a: [1, 2, 3] }; + const result = merge(target, source); + + expect(result).toEqual({ a: [1, 2, 3] }); + }); + + it("(curried) should handle merging with null values", () => { + const target = { a: null }; + const source = { a: [1, 2, 3] }; + const result = merge(source)(target); + + expect(result).toEqual({ a: [1, 2, 3] }); + }); + + it( + "(non-curried) should handle merging arrays into non-array target values", + () => { + const numbers = [1, 2, 3]; + + const target = { a: 1, b: {} }; + const source = { b: numbers, c: 4 }; + const result = merge(target, source); + + expect(result).toEqual({ a: 1, b: numbers, c: 4 }); + expect(result.b).not.toBe(numbers); + } + ); + + it("(curried) should handle merging arrays into non-array target values", () => { + const numbers = [1, 2, 3]; + + const target = { a: 1, b: {} }; + const source = { b: numbers, c: 4 }; + const result = merge(source)(target); + + expect(result).toEqual({ a: 1, b: numbers, c: 4 }); + expect(result.b).not.toBe(numbers); + }); + + it("(non-curried) should create new plain object when merged", () => { + const plainObject = { b: 2 } as const; + + const target = {}; + const source = { a: plainObject }; + const result = merge(target, source); + + expect(result).toEqual({ a: plainObject }); + expect(result.a).not.toBe(plainObject); + }); + + it("(curried) should create new plain object when merged", () => { + const plainObject = { b: 2 } as const; + + const target = {}; + const source = { a: plainObject }; + const result = merge(source)(target); + + expect(result).toEqual({ a: plainObject }); + expect(result.a).not.toBe(plainObject); + }); + + it( + "(non-curried) should handle merging values that are neither arrays nor plain objects", + () => { + const date = new Date(); + const target = {}; + const source = { a: date }; + const result = merge(target, source); + + expect(result).toEqual({ a: date }); + // unlike arrays and plain objects, the original value is used. + expect(result.a).toBe(date); + } + ); + + it("(curried) should handle merging values that are neither arrays nor plain objects", () => { + const date = new Date(); + const target = {}; + const source = { a: date }; + const result = merge(source)(target); + + expect(result).toEqual({ a: date }); + // unlike arrays and plain objects, the original value is used. + expect(result.a).toBe(date); + }); + + it( + "(non-curried) should not overwrite existing values with undefined from source", + () => { + const target = { a: 1, b: 2 }; + const source = { b: undefined, c: 3 }; + const result = merge(target, source); + + expect(result).toEqual({ a: 1, b: 2, c: 3 }); + } + ); + + it("(curried) should not overwrite existing values with undefined from source", () => { + const target = { a: 1, b: 2 }; + const source = { b: undefined, c: 3 }; + const result = merge(source)(target); + + expect(result).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it( + "(non-curried) should handle merging of deeply nested objects with arrays and objects", + () => { + const target = { a: { b: { c: [1] } } }; + const source = { a: { b: { c: [2], d: 3 }, e: [4] } }; + const result = merge(target, source); + + expect(result).toEqual({ a: { b: { c: [2], d: 3 }, e: [4] } }); + } + ); + + it("(curried) should handle merging of deeply nested objects with arrays and objects", () => { + const target = { a: { b: { c: [1] } } }; + const source = { a: { b: { c: [2], d: 3 }, e: [4] } }; + const result = merge(source)(target); + + expect(result).toEqual({ a: { b: { c: [2], d: 3 }, e: [4] } }); + }); +}); diff --git a/src/fp/object/merge.ts b/src/fp/object/merge.ts new file mode 100644 index 000000000..a9f60e163 --- /dev/null +++ b/src/fp/object/merge.ts @@ -0,0 +1,101 @@ +import { merge as mergeToolkit } from '../../object/merge'; + +/** + * Merges the properties of the source object into the target object. + * + * This function performs a deep merge, meaning nested objects and arrays are merged recursively. + * If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged. + * If a property in the source object is undefined, it will not overwrite a defined property in the target object. + * + * Note that this function mutates the target object. + * + * @param {T} target - The target object into which the source object properties will be merged. This object is modified in place. + * @param {S} source - The source object whose properties will be merged into the target object. + * @returns {T & S} The updated target object with properties from the source object merged in. + * + * @template T - Type of the target object. + * @template S - Type of the source object. + * + * @example + * const target = { a: 1, b: { x: 1, y: 2 } }; + * const source = { b: { y: 3, z: 4 }, c: 5 }; + * + * const result = merge(target, source); + * console.log(result); + * // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 } + * + * @example + * const target = { a: [1, 2], b: { x: 1 } }; + * const source = { a: [3], b: { y: 2 } }; + * + * const result = merge(target, source); + * console.log(result); + * // Output: { a: [3, 2], b: { x: 1, y: 2 } } + * + * @example + * const target = { a: null }; + * const source = { a: [1, 2, 3] }; + * + * const result = merge(target, source); + * console.log(result); + * // Output: { a: [1, 2, 3] } + */ +export function merge, S extends Record>( + target: T, + source: S +): T & S; + +/** + * Merges the properties of the source object into the target object. + * + * This function performs a deep merge, meaning nested objects and arrays are merged recursively. + * If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged. + * If a property in the source object is undefined, it will not overwrite a defined property in the target object. + * + * Note that this function mutates the target object. + * + * @param {S} source - The source object whose properties will be merged into the target object. + * @returns {(target: T) => T & S} A function that receive the target object into which the source object properties will be merged. This object is modified in place as argument and returns the updated target object with properties from the source object merged in. + * + * @template T - Type of the target object. + * @template S - Type of the source object. + * + * @example + * const target = { a: 1, b: { x: 1, y: 2 } }; + * const source = { b: { y: 3, z: 4 }, c: 5 }; + * + * const result = merge(source)(target); + * console.log(result); + * // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 } + * + * @example + * const target = { a: [1, 2], b: { x: 1 } }; + * const source = { a: [3], b: { y: 2 } }; + * + * const result = merge(target, source); + * console.log(result); + * // Output: { a: [3, 2], b: { x: 1, y: 2 } } + * + * @example + * const target = { a: null }; + * const source = { a: [1, 2, 3] }; + * + * const result = merge(target, source); + * console.log(result); + * // Output: { a: [1, 2, 3] } + */ +export function merge, S extends Record>( + source: S +): (target: T) => T & S; + +export function merge, S extends Record>( + targetOrSource: T | S, + source?: S +) { + if (source == null) { + return (target: T) => merge(target, targetOrSource as S); + } + + const target = targetOrSource as T; + return mergeToolkit(target, source); +} diff --git a/src/fp/object/mergeWith.spec.ts b/src/fp/object/mergeWith.spec.ts new file mode 100644 index 000000000..a0b15da9e --- /dev/null +++ b/src/fp/object/mergeWith.spec.ts @@ -0,0 +1,100 @@ +import { describe, expect, it } from 'vitest'; +import { mergeWith } from './mergeWith'; + +describe('mergeWith', () => { + it('(non-curried) should merge properties from source object into target object using custom merge function', () => { + const target1 = { a: 1, b: 2 }; + const source1 = { b: 3, c: 4 }; + + const result1 = mergeWith(target1, source1, (targetValue, sourceValue) => { + if (typeof targetValue === 'number' && typeof sourceValue === 'number') { + return targetValue + sourceValue; + } + }); + + expect(result1).toEqual({ a: 1, b: 5, c: 4 }); + + const target2 = { a: [1], b: [2] }; + const source2 = { a: [3], b: [4] }; + + const result2 = mergeWith(target2, source2, (objValue, srcValue) => { + if (Array.isArray(objValue)) { + return objValue.concat(srcValue); + } + }); + + expect(result2).toEqual({ a: [1, 3], b: [2, 4] }); + }); + + it('(curried) should merge properties from source object into target object using custom merge function', () => { + const target1 = { a: 1, b: 2 }; + const source1 = { b: 3, c: 4 }; + + const result1 = mergeWith(source1, (targetValue, sourceValue) => { + if (typeof targetValue === 'number' && typeof sourceValue === 'number') { + return targetValue + sourceValue; + } + })(target1); + + expect(result1).toEqual({ a: 1, b: 5, c: 4 }); + + const target2 = { a: [1], b: [2] }; + const source2 = { a: [3], b: [4] }; + + const result2 = mergeWith(source2, (objValue, srcValue) => { + if (Array.isArray(objValue)) { + return objValue.concat(srcValue); + } + })(target2); + + expect(result2).toEqual({ a: [1, 3], b: [2, 4] }); + }); + + it('(non-curried) should use custom merge function for nested objects', () => { + const target = { a: { x: 1, y: 1 }, b: 2 }; + const source = { a: { y: 2 }, b: 3 }; + + const result = mergeWith(target, source, (targetValue, sourceValue) => { + if (typeof targetValue === 'number' && typeof sourceValue === 'number') { + return targetValue + sourceValue; + } + }); + + expect(result).toEqual({ a: { x: 1, y: 3 }, b: 5 }); + + const target2 = { a: { c: [1] }, b: [2] }; + const source2 = { a: { c: [3] }, b: [4] }; + + const result2 = mergeWith(target2, source2, (objValue, srcValue) => { + if (Array.isArray(objValue)) { + return objValue.concat(srcValue); + } + }); + + expect(result2).toEqual({ a: { c: [1, 3] }, b: [2, 4] }); + }); + + it('(curried) should use custom merge function for nested objects', () => { + const target = { a: { x: 1, y: 1 }, b: 2 }; + const source = { a: { y: 2 }, b: 3 }; + + const result = mergeWith(source, (targetValue, sourceValue) => { + if (typeof targetValue === 'number' && typeof sourceValue === 'number') { + return targetValue + sourceValue; + } + })(target); + + expect(result).toEqual({ a: { x: 1, y: 3 }, b: 5 }); + + const target2 = { a: { c: [1] }, b: [2] }; + const source2 = { a: { c: [3] }, b: [4] }; + + const result2 = mergeWith(source2, (objValue, srcValue) => { + if (Array.isArray(objValue)) { + return objValue.concat(srcValue); + } + })(target2); + + expect(result2).toEqual({ a: { c: [1, 3] }, b: [2, 4] }); + }); +}); diff --git a/src/fp/object/mergeWith.ts b/src/fp/object/mergeWith.ts new file mode 100644 index 000000000..b3b21eb2e --- /dev/null +++ b/src/fp/object/mergeWith.ts @@ -0,0 +1,64 @@ +import { mergeWith as mergeWithToolkit } from '../../object/mergeWith'; + +/** + * Creates a function that merges objects with customizer function. + * + * @template T - The type of the source object. + * @template U - The type of the target object. + * @param {U} source - The source object. + * @param {(objValue: any, srcValue: any, key: string, object: T, source: U) => any} customizer - The function to customize merging properties. + * @returns {(obj: T) => T & U} A function that takes an object and returns a new merged object. + * + * @example + * const object = { 'a': [1], 'b': [2] }; + * const other = { 'a': [3], 'b': [4] }; + * const mergeArrays = mergeWith(other, (objValue, srcValue) => { + * if (Array.isArray(objValue)) { + * return objValue.concat(srcValue); + * } + * }); + * const result = mergeArrays(object); + * // => { 'a': [1, 3], 'b': [2, 4] } + */ +export function mergeWith( + source: U, + customizer: (objValue: any, srcValue: any, key: string, object: T, source: U) => any +): (obj: T) => T & U; + +/** + * Merges objects with customizer function. + * + * @template T - The type of the target object. + * @template U - The type of the source object. + * @param {T} object - The target object. + * @param {U} source - The source object. + * @param {(objValue: any, srcValue: any, key: string, object: T, source: U) => any} customizer - The function to customize merging properties. + * @returns {T & U} Returns the merged object. + * + * @example + * const object = { 'a': [1], 'b': [2] }; + * const other = { 'a': [3], 'b': [4] }; + * const result = mergeWith(object, other, (objValue, srcValue) => { + * if (Array.isArray(objValue)) { + * return objValue.concat(srcValue); + * } + * }); + * // => { 'a': [1, 3], 'b': [2, 4] } + */ +export function mergeWith( + object: T, + source: U, + customizer: (objValue: any, srcValue: any, key: string, object: T, source: U) => any +): T & U; + +export function mergeWith( + objectOrSource: T | U, + sourceOrCustomizer: U | ((objValue: any, srcValue: any, key: string, object: T, source: U) => any), + customizer?: (objValue: any, srcValue: any, key: string, object: T, source: U) => any +) { + if (typeof sourceOrCustomizer === 'function') { + return (object: T) => mergeWith(object, objectOrSource as U, sourceOrCustomizer); + } + + return mergeWithToolkit(objectOrSource as T, sourceOrCustomizer, customizer!); +} diff --git a/src/fp/object/omit.spec.ts b/src/fp/object/omit.spec.ts new file mode 100644 index 000000000..dea20db95 --- /dev/null +++ b/src/fp/object/omit.spec.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; +import { omit } from './omit'; + +describe('omit', () => { + it('(non-curried) should omit properties from an object', () => { + const object = { foo: 1, bar: 2, baz: 3 }; + const result = omit(object, ['foo', 'bar']); + expect(result).toEqual({ baz: 3 }); + }); + + it('(curried) should omit properties from an object', () => { + const object = { foo: 1, bar: 2, baz: 3 }; + const result = omit(['foo', 'bar'])(object); + expect(result).toEqual({ baz: 3 }); + }); + + it('(non-curried) should return an empty object if all keys are omitted', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = omit(obj, ['a', 'b', 'c']); + expect(result).toEqual({}); + }); + + it('(curried) should return an empty object if all keys are omitted', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = omit(['a', 'b', 'c'])(obj); + expect(result).toEqual({}); + }); + + it('(non-curried) should return the same object if no keys are omitted', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = omit(obj, []); + expect(result).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it('(curried) should return the same object if no keys are omitted', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = omit([])(obj); + expect(result).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it('(non-curried) should not affect the original object', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = omit(obj, ['b']); + expect(result).toEqual({ a: 1, c: 3 }); + expect(obj).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it('(curried) should not affect the original object', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = omit(['b'])(obj); + expect(result).toEqual({ a: 1, c: 3 }); + expect(obj).toEqual({ a: 1, b: 2, c: 3 }); + }); +}); diff --git a/src/fp/object/omit.ts b/src/fp/object/omit.ts new file mode 100644 index 000000000..73532acb4 --- /dev/null +++ b/src/fp/object/omit.ts @@ -0,0 +1,50 @@ +import { omit as omitToolkit } from '../../object/omit'; + +/** + * Creates a new object with specified keys omitted. + * + * This function takes an object and an array of keys, and returns a new object that + * excludes the properties corresponding to the specified keys. + * + * @template T - The type of object. + * @template K - The type of keys in object. + * @param {T} obj - The object to omit keys from. + * @param {K[]} keys - An array of keys to be omitted from the object. + * @returns {Omit} A new object with the specified keys omitted. + * + * @example + * const obj = { a: 1, b: 2, c: 3 }; + * const result = omit(obj, ['b', 'c']); + * // result will be { a: 1 } + */ +export function omit, K extends keyof T>(obj: T, keys: readonly K[]): Omit; + +/** + * Creates a new object with specified keys omitted. + * + * This function takes an object and an array of keys, and returns a new object that + * excludes the properties corresponding to the specified keys. + * + * @template T - The type of object. + * @template K - The type of keys in object. + * @param {K[]} keys - An array of keys to be omitted from the object. + * @returns {(obj: T) => Omit} A function that receive the object to omit keys from as argument and returns a new object with the specified keys omitted. + * + * @example + * const obj = { a: 1, b: 2, c: 3 }; + * const result = omit(['b', 'c'])(obj); + * // result will be { a: 1 } + */ +export function omit, K extends keyof T>(keys: readonly K[]): (obj: T) => Omit; + +export function omit, K extends keyof T>( + objOrKeys: T | readonly K[], + keys?: readonly K[] +) { + if (keys == null) { + return (obj: T) => omit(obj, objOrKeys as readonly K[]); + } + + const obj = objOrKeys as T; + return omitToolkit(obj, keys); +} diff --git a/src/fp/object/omitBy.spec.ts b/src/fp/object/omitBy.spec.ts new file mode 100644 index 000000000..b99748550 --- /dev/null +++ b/src/fp/object/omitBy.spec.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from 'vitest'; +import { omitBy } from './omitBy'; + +describe('omitBy', () => { + it('(non-curried) should omit properties based on the predicate function', () => { + const obj = { a: 1, b: 'omit', c: 3 }; + const shouldOmit = (value: number | string) => typeof value === 'string'; + const result = omitBy(obj, shouldOmit); + expect(result).toEqual({ a: 1, c: 3 }); + }); + + it('(curried) should omit properties based on the predicate function', () => { + const obj = { a: 1, b: 'omit', c: 3 }; + const shouldOmit = (value: number | string) => typeof value === 'string'; + const result = omitBy(shouldOmit)(obj); + expect(result).toEqual({ a: 1, c: 3 }); + }); + + it('(non-curried) should return an empty object if all properties are omitted', () => { + const obj = { a: 'omit', b: 'omit' }; + const shouldOmit = (value: string) => typeof value === 'string'; + const result = omitBy(obj, shouldOmit); + expect(result).toEqual({}); + }); + + it('(curried) should return an empty object if all properties are omitted', () => { + const obj = { a: 'omit', b: 'omit' }; + const shouldOmit = (value: string) => typeof value === 'string'; + const result = omitBy(shouldOmit)(obj); + expect(result).toEqual({}); + }); + + it('(non-curried) should return the same object if no properties are omitted', () => { + const obj = { a: 1, b: 2, c: 3 }; + const shouldOmit = (value: number) => typeof value === 'string'; + const result = omitBy(obj, shouldOmit); + expect(result).toEqual(obj); + }); + + it('(curried) should return the same object if no properties are omitted', () => { + const obj = { a: 1, b: 2, c: 3 }; + const shouldOmit = (value: number) => typeof value === 'string'; + const result = omitBy(shouldOmit)(obj); + expect(result).toEqual(obj); + }); + + it('(non-curried) should work with an empty object', () => { + const obj = {}; + const shouldOmit = (value: never) => value; + const result = omitBy(obj, shouldOmit); + expect(result).toEqual({}); + }); + + it('(curried) should work with an empty object', () => { + const obj = {}; + const shouldOmit = (value: never) => value; + const result = omitBy<{}>(shouldOmit)(obj); + expect(result).toEqual({}); + }); + + it('(non-curried) should work with nested objects', () => { + const obj = { a: 1, b: { nested: 'omit' }, c: 3 }; + const shouldOmit = (_: number | { nested: string }, key: string) => key === 'b'; + const result = omitBy(obj, shouldOmit); + expect(result).toEqual({ a: 1, c: 3 }); + }); + + it('(curried) should work with nested objects', () => { + const obj = { a: 1, b: { nested: 'omit' }, c: 3 }; + const shouldOmit = (_: number | { nested: string }, key: string) => key === 'b'; + const result = omitBy(shouldOmit)(obj); + expect(result).toEqual({ a: 1, c: 3 }); + }); +}); diff --git a/src/fp/object/omitBy.ts b/src/fp/object/omitBy.ts new file mode 100644 index 000000000..9eab47d34 --- /dev/null +++ b/src/fp/object/omitBy.ts @@ -0,0 +1,59 @@ +import { omitBy as omitByToolkit } from '../../object/omitBy'; + +/** + * Creates a new object composed of the properties that do not satisfy the predicate function. + * + * This function takes an object and a predicate function, and returns a new object that + * includes only the properties for which the predicate function returns false. + * + * @template T - The type of object. + * @param {T} obj - The object to omit properties from. + * @param {(value: T[string], key: keyof T) => boolean} shouldOmit - A predicate function that determines + * whether a property should be omitted. It takes the property's key and value as arguments and returns `true` + * if the property should be omitted, and `false` otherwise. + * @returns {Partial} A new object with the properties that do not satisfy the predicate function. + * + * @example + * const obj = { a: 1, b: 'omit', c: 3 }; + * const shouldOmit = (value) => typeof value === 'string'; + * const result = omitBy(obj, shouldOmit); + * // result will be { a: 1, c: 3 } + */ +export function omitBy>( + obj: T, + shouldOmit: (value: T[keyof T], key: keyof T) => boolean +): Partial; + +/** + * Creates a new object composed of the properties that do not satisfy the predicate function. + * + * This function takes an object and a predicate function, and returns a new object that + * includes only the properties for which the predicate function returns false. + * + * @template T - The type of object. + * @param {(value: T[string], key: keyof T) => boolean} shouldOmit - A predicate function that determines + * whether a property should be omitted. It takes the property's key and value as arguments and returns `true` + * if the property should be omitted, and `false` otherwise. + * @returns {(obj: T) => Partial} A function that receive the object to omit properties from as argument and returns a new object with the properties that do not satisfy the predicate function. + * + * @example + * const obj = { a: 1, b: 'omit', c: 3 }; + * const shouldOmit = (value) => typeof value === 'string'; + * const result = omitBy(shouldOmit)(obj); + * // result will be { a: 1, c: 3 } + */ +export function omitBy>( + shouldOmit: (value: T[keyof T], key: keyof T) => boolean +): (obj: T) => Partial; + +export function omitBy>( + objOrShouldOmit: T | ((value: T[keyof T], key: keyof T) => boolean), + shouldOmit?: (value: T[keyof T], key: keyof T) => boolean +) { + if (shouldOmit == null) { + return (obj: T) => omitBy(obj, objOrShouldOmit as (value: T[keyof T], key: keyof T) => boolean); + } + + const obj = objOrShouldOmit as T; + return omitByToolkit(obj, shouldOmit); +} diff --git a/src/fp/object/pick.spec.ts b/src/fp/object/pick.spec.ts new file mode 100644 index 000000000..6bc2787ef --- /dev/null +++ b/src/fp/object/pick.spec.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from 'vitest'; +import { pick } from './pick'; + +describe('pick', () => { + it("(non-curried) should pick properties from an object", () => { + const object = { foo: 1, bar: 2, baz: 3 }; + const result = pick(object, ['foo', 'bar']); + expect(result).toEqual({ foo: 1, bar: 2 }); + }); + + it("(curried) should pick properties from an object", () => { + const object = { foo: 1, bar: 2, baz: 3 }; + const result = pick(['foo', 'bar'])(object); + expect(result).toEqual({ foo: 1, bar: 2 }); + }); + + it( + "(non-curried) should return the same object if all keys are picked", + () => { + const object = { a: 1, b: 2, c: 3 }; + const result = pick(object, ['a', 'b', 'c']); + expect(result).toEqual(object); + } + ); + + it("(curried) should return the same object if all keys are picked", () => { + const object = { a: 1, b: 2, c: 3 }; + const result = pick(['a', 'b', 'c'])(object); + expect(result).toEqual(object); + }); + + it( + "(non-curried) should return an empty object if the key array is empty", + () => { + const object = { a: 1, b: 2, c: 3 }; + const result = pick(object, []); + expect(result).toEqual({}); + } + ); + + it("(curried) should return an empty object if the key array is empty", () => { + const object = { a: 1, b: 2, c: 3 }; + const result = pick([])(object); + expect(result).toEqual({}); + }); + + it("(non-curried) should work with nested objects", () => { + const object = { a: 1, b: { nested: 'pick' }, c: 3 }; + const result = pick(object, ['a', 'b', 'c']); + expect(result).toEqual({ a: 1, b: { nested: 'pick' }, c: 3 }); + }); + + it("(curried) should work with nested objects", () => { + const object = { a: 1, b: { nested: 'pick' }, c: 3 }; + const result = pick(['a', 'b', 'c'])(object); + expect(result).toEqual({ a: 1, b: { nested: 'pick' }, c: 3 }); + }); + + it("(non-curried) should not pick from nonexistent keys", () => { + const obj: { a?: unknown } = {}; + const result = pick(obj, ['a']); + + expect(Reflect.ownKeys(result)).toEqual([]); + }); + + it("(curried) should not pick from nonexistent keys", () => { + const obj: { a?: unknown } = {}; + const result = pick(['a'])(obj); + + expect(Reflect.ownKeys(result)).toEqual([]); + }); +}); diff --git a/src/fp/object/pick.ts b/src/fp/object/pick.ts new file mode 100644 index 000000000..d11bb7c02 --- /dev/null +++ b/src/fp/object/pick.ts @@ -0,0 +1,50 @@ +import { pick as pickToolkit } from '../../object/pick'; + +/** + * Creates a new object composed of the picked object properties. + * + * This function takes an object and an array of keys, and returns a new object that + * includes only the properties corresponding to the specified keys. + * + * @template T - The type of object. + * @template K - The type of keys in object. + * @param {T} obj - The object to pick keys from. + * @param {K[]} keys - An array of keys to be picked from the object. + * @returns {Pick} A new object with the specified keys picked. + * + * @example + * const obj = { a: 1, b: 2, c: 3 }; + * const result = pick(obj, ['a', 'c']); + * // result will be { a: 1, c: 3 } + */ +export function pick, K extends keyof T>(obj: T, keys: readonly K[]): Pick; + +/** + * Creates a new object composed of the picked object properties. + * + * This function takes an object and an array of keys, and returns a new object that + * includes only the properties corresponding to the specified keys. + * + * @template T - The type of object. + * @template K - The type of keys in object. + * @param {K[]} keys - An array of keys to be picked from the object. + * @returns {(obj: T) => Pick} A function that receive the object to pick keys from as argument and returns a new object with the specified keys picked. + * + * @example + * const obj = { a: 1, b: 2, c: 3 }; + * const result = pick(['a', 'c'])(obj); + * // result will be { a: 1, c: 3 } + */ +export function pick, K extends keyof T>(keys: readonly K[]): (obj: T) => Pick; + +export function pick, K extends keyof T>( + objOrKeys: T | readonly K[], + keys?: readonly K[] +) { + if (keys == null) { + return (obj: T) => pick(obj, objOrKeys as readonly K[]); + } + + const obj = objOrKeys as T; + return pickToolkit(obj, keys); +} diff --git a/src/fp/object/pickBy.spec.ts b/src/fp/object/pickBy.spec.ts new file mode 100644 index 000000000..2660b0d95 --- /dev/null +++ b/src/fp/object/pickBy.spec.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from 'vitest'; +import { pickBy } from './pickBy'; + +describe('pickBy', () => { + it( + "(non-curried) should pick properties based on the predicate function", + () => { + const obj = { a: 1, b: 'pick', c: 3 }; + const shouldPick = (value: string | number) => typeof value === 'string'; + const result = pickBy(obj, shouldPick); + expect(result).toEqual({ b: 'pick' }); + } + ); + + it("(curried) should pick properties based on the predicate function", () => { + const obj = { a: 1, b: 'pick', c: 3 }; + const shouldPick = (value: string | number) => typeof value === 'string'; + const result = pickBy(shouldPick)(obj); + expect(result).toEqual({ b: 'pick' }); + }); + + it( + "(non-curried) should return an empty object if no properties satisfy the predicate", + () => { + const obj = { a: 1, b: 2, c: 3 }; + const shouldPick = (value: number) => typeof value === 'string'; + const result = pickBy(obj, shouldPick); + expect(result).toEqual({}); + } + ); + + it("(curried) should return an empty object if no properties satisfy the predicate", () => { + const obj = { a: 1, b: 2, c: 3 }; + const shouldPick = (value: number) => typeof value === 'string'; + const result = pickBy(shouldPick)(obj); + expect(result).toEqual({}); + }); + + it( + "(non-curried) should return the same object if all properties satisfy the predicate", + () => { + const obj = { a: 'pick', b: 'pick', c: 'pick' }; + const shouldPick = (value: string) => typeof value === 'string'; + const result = pickBy(obj, shouldPick); + expect(result).toEqual(obj); + } + ); + + it("(curried) should return the same object if all properties satisfy the predicate", () => { + const obj = { a: 'pick', b: 'pick', c: 'pick' }; + const shouldPick = (value: string) => typeof value === 'string'; + const result = pickBy(shouldPick)(obj); + expect(result).toEqual(obj); + }); + + it("(non-curried) should work with an empty object", () => { + const obj = {}; + const shouldPick = (value: never) => value; + const result = pickBy(obj, shouldPick); + expect(result).toEqual({}); + }); + + it("(curried) should work with an empty object", () => { + const obj = {}; + const shouldPick = (value: never) => value; + const result = pickBy<{ [key: string]: never }>(shouldPick)(obj); + expect(result).toEqual({}); + }); + + it("(non-curried) should work with nested objects", () => { + const obj = { a: 1, b: { nested: 'pick' }, c: 3 }; + const shouldPick = (value: number | { nested: string }, key: string) => key === 'b'; + const result = pickBy(obj, shouldPick); + expect(result).toEqual({ b: { nested: 'pick' } }); + }); + + it("(curried) should work with nested objects", () => { + const obj = { a: 1, b: { nested: 'pick' }, c: 3 }; + const shouldPick = (value: number | { nested: string }, key: string) => key === 'b'; + const result = pickBy(shouldPick)(obj); + expect(result).toEqual({ b: { nested: 'pick' } }); + }); +}); diff --git a/src/fp/object/pickBy.ts b/src/fp/object/pickBy.ts new file mode 100644 index 000000000..5c037675d --- /dev/null +++ b/src/fp/object/pickBy.ts @@ -0,0 +1,59 @@ +import { pickBy as pickByToolkit } from '../../object/pickBy'; + +/** + * Creates a new object composed of the properties that satisfy the predicate function. + * + * This function takes an object and a predicate function, and returns a new object that + * includes only the properties for which the predicate function returns true. + * + * @template T - The type of object. + * @param {T} obj - The object to pick properties from. + * @param {(value: T[keyof T], key: keyof T) => boolean} shouldPick - A predicate function that determines + * whether a property should be picked. It takes the property's key and value as arguments and returns `true` + * if the property should be picked, and `false` otherwise. + * @returns {Partial} A new object with the properties that satisfy the predicate function. + * + * @example + * const obj = { a: 1, b: 'pick', c: 3 }; + * const shouldPick = (value) => typeof value === 'string'; + * const result = pickBy(obj, shouldPick); + * // result will be { b: 'pick' } + */ +export function pickBy>( + obj: T, + shouldPick: (value: T[keyof T], key: keyof T) => boolean +): Partial; + +/** + * Creates a new object composed of the properties that satisfy the predicate function. + * + * This function takes an object and a predicate function, and returns a new object that + * includes only the properties for which the predicate function returns true. + * + * @template T - The type of object. + * @param {(value: T[keyof T], key: keyof T) => boolean} shouldPick - A predicate function that determines + * whether a property should be picked. It takes the property's key and value as arguments and returns `true` + * if the property should be picked, and `false` otherwise. + * @returns {(obj: T) => Partial} A function that receive the object to pick properties from as argument and returns a new object with the properties that satisfy the predicate function. + * + * @example + * const obj = { a: 1, b: 'pick', c: 3 }; + * const shouldPick = (value) => typeof value === 'string'; + * const result = pickBy(shouldPick)(obj); + * // result will be { b: 'pick' } + */ +export function pickBy>( + shouldPick: (value: T[keyof T], key: keyof T) => boolean +): (obj: T) => Partial; + +export function pickBy>( + objOrShouldPick: T | ((value: T[keyof T], key: keyof T) => boolean), + shouldPick?: (value: T[keyof T], key: keyof T) => boolean +) { + if (shouldPick == null) { + return (obj: T) => pickBy(obj, objOrShouldPick as (value: T[keyof T], key: keyof T) => boolean); + } + + const obj = objOrShouldPick as T; + return pickByToolkit(obj, shouldPick); +} diff --git a/src/fp/object/toMerged.spec.ts b/src/fp/object/toMerged.spec.ts new file mode 100644 index 000000000..3260d34de --- /dev/null +++ b/src/fp/object/toMerged.spec.ts @@ -0,0 +1,179 @@ +import { describe, expect, it } from 'vitest'; +import { toMerged } from './toMerged'; + +describe('toMerged', () => { + it( + "(non-curried) should merge properties from source object into target object", + () => { + const target = { a: 1, b: 2 }; + const source = { b: 3, c: 4 }; + const result = toMerged(target, source); + + expect(result).toEqual({ a: 1, b: 3, c: 4 }); + expect(target).toEqual({ a: 1, b: 2 }); + } + ); + + it("(curried) should merge properties from source object into target object", () => { + const target = { a: 1, b: 2 }; + const source = { b: 3, c: 4 }; + const result = toMerged(source)(target); + + expect(result).toEqual({ a: 1, b: 3, c: 4 }); + expect(target).toEqual({ a: 1, b: 2 }); + }); + + it("(non-curried) should deeply merge nested objects", () => { + const target = { a: { x: 1, y: 2 }, b: 2 }; + const source = { a: { y: 3, z: 4 }, c: 5 }; + const result = toMerged(target, source); + + expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 2, c: 5 }); + expect(target).toEqual({ a: { x: 1, y: 2 }, b: 2 }); + + const names = { + characters: [{ name: 'barney' }, { name: 'fred' }], + }; + + const ages = { + characters: [{ age: 36 }, { age: 40 }], + }; + + const heights = { + characters: [{ height: '5\'4"' }, { height: '5\'5"' }], + }; + + const expected = { + characters: [ + { name: 'barney', age: 36, height: '5\'4"' }, + { name: 'fred', age: 40, height: '5\'5"' }, + ], + }; + + expect(toMerged(toMerged(names, ages), heights)).toEqual(expected); + expect(names).toEqual({ + characters: [{ name: 'barney' }, { name: 'fred' }], + }); + + const target2 = { a: [1, 2], b: { x: 1 } }; + const source2 = { a: [3], b: { y: 2 } }; + expect(toMerged(target2, source2)).toEqual({ a: [3, 2], b: { x: 1, y: 2 } }); + expect(target2).toEqual({ a: [1, 2], b: { x: 1 } }); + }); + + it("(curried) should deeply merge nested objects", () => { + const target = { a: { x: 1, y: 2 }, b: 2 }; + const source = { a: { y: 3, z: 4 }, c: 5 }; + const result = toMerged(source)(target); + + expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 2, c: 5 }); + expect(target).toEqual({ a: { x: 1, y: 2 }, b: 2 }); + + const names = { + characters: [{ name: 'barney' }, { name: 'fred' }], + }; + + const ages = { + characters: [{ age: 36 }, { age: 40 }], + }; + + const heights = { + characters: [{ height: '5\'4"' }, { height: '5\'5"' }], + }; + + const expected = { + characters: [ + { name: 'barney', age: 36, height: '5\'4"' }, + { name: 'fred', age: 40, height: '5\'5"' }, + ], + }; + + expect(toMerged(heights)(toMerged(ages)(names))).toEqual(expected); + expect(names).toEqual({ + characters: [{ name: 'barney' }, { name: 'fred' }], + }); + + const target2 = { a: [1, 2], b: { x: 1 } }; + const source2 = { a: [3], b: { y: 2 } }; + expect(toMerged(source2)(target2)).toEqual({ a: [3, 2], b: { x: 1, y: 2 } }); + expect(target2).toEqual({ a: [1, 2], b: { x: 1 } }); + }); + + it("(non-curried) should merge arrays deeply", () => { + const target = { a: [1, 2] }; + const source = { a: [3, 4] }; + const result = toMerged(target, source); + + expect(result).toEqual({ a: [3, 4] }); + expect(target).toEqual({ a: [1, 2] }); + }); + + it("(curried) should merge arrays deeply", () => { + const target = { a: [1, 2] }; + const source = { a: [3, 4] }; + const result = toMerged(source)(target); + + expect(result).toEqual({ a: [3, 4] }); + expect(target).toEqual({ a: [1, 2] }); + }); + + it("(non-curried) should handle merging with null values", () => { + const target = { a: null }; + const source = { a: [1, 2, 3] }; + const result = toMerged(target, source); + + expect(result).toEqual({ a: [1, 2, 3] }); + expect(target).toEqual({ a: null }); + }); + + it("(curried) should handle merging with null values", () => { + const target = { a: null }; + const source = { a: [1, 2, 3] }; + const result = toMerged(source)(target); + + expect(result).toEqual({ a: [1, 2, 3] }); + expect(target).toEqual({ a: null }); + }); + + it( + "(non-curried) should not overwrite existing values with undefined from source", + () => { + const target = { a: 1, b: 2 }; + const source = { b: undefined, c: 3 }; + const result = toMerged(target, source); + + expect(result).toEqual({ a: 1, b: 2, c: 3 }); + expect(target).toEqual({ a: 1, b: 2 }); + } + ); + + it("(curried) should not overwrite existing values with undefined from source", () => { + const target = { a: 1, b: 2 }; + const source = { b: undefined, c: 3 }; + const result = toMerged(source)(target); + + expect(result).toEqual({ a: 1, b: 2, c: 3 }); + expect(target).toEqual({ a: 1, b: 2 }); + }); + + it( + "(non-curried) should handle merging of deeply nested objects with arrays and objects", + () => { + const target = { a: { b: { c: [1] } } }; + const source = { a: { b: { c: [2], d: 3 }, e: [4] } }; + const result = toMerged(target, source); + + expect(result).toEqual({ a: { b: { c: [2], d: 3 }, e: [4] } }); + expect(target).toEqual({ a: { b: { c: [1] } } }); + } + ); + + it("(curried) should handle merging of deeply nested objects with arrays and objects", () => { + const target = { a: { b: { c: [1] } } }; + const source = { a: { b: { c: [2], d: 3 }, e: [4] } }; + const result = toMerged(source)(target); + + expect(result).toEqual({ a: { b: { c: [2], d: 3 }, e: [4] } }); + expect(target).toEqual({ a: { b: { c: [1] } } }); + }); +}); diff --git a/src/fp/object/toMerged.ts b/src/fp/object/toMerged.ts new file mode 100644 index 000000000..2e886351b --- /dev/null +++ b/src/fp/object/toMerged.ts @@ -0,0 +1,105 @@ +import { toMerged as toMergedToolkit } from '../../object/toMerged'; + +/** + * Merges the properties of the source object into a deep clone of the target object. + * Unlike `merge`, This function does not modify the original target object. + * + * This function performs a deep merge, meaning nested objects and arrays are merged recursively. + * + * - If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged. + * - If a property in the source object is undefined, it will not overwrite a defined property in the target object. + * + * Note that this function does not mutate the target object. + * + * @param {T} target - The target object to be cloned and merged into. This object is not modified directly. + * @param {S} source - The source object whose properties will be merged into the cloned target object. + * @returns {T & S} A new object with properties from the source object merged into a deep clone of the target object. + * + * @template T - Type of the target object. + * @template S - Type of the source object. + * + * @example + * const target = { a: 1, b: { x: 1, y: 2 } }; + * const source = { b: { y: 3, z: 4 }, c: 5 }; + * + * const result = toMerged(target, source); + * console.log(result); + * // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 } + * + * @example + * const target = { a: [1, 2], b: { x: 1 } }; + * const source = { a: [3], b: { y: 2 } }; + * + * const result = toMerged(target, source); + * console.log(result); + * // Output: { a: [3, 2], b: { x: 1, y: 2 } } + * + * @example + * const target = { a: null }; + * const source = { a: [1, 2, 3] }; + * + * const result = toMerged(target, source); + * console.log(result); + * // Output: { a: [1, 2, 3] } + */ +export function toMerged, S extends Record>( + target: T, + source: S +): T & S; + +/** + * Merges the properties of the source object into a deep clone of the target object. + * Unlike `merge`, This function does not modify the original target object. + * + * This function performs a deep merge, meaning nested objects and arrays are merged recursively. + * + * - If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged. + * - If a property in the source object is undefined, it will not overwrite a defined property in the target object. + * + * Note that this function does not mutate the target object. + * + * @param {S} source - The source object whose properties will be merged into the cloned target object. + * @returns {(target: T) => T & S} A function that receive the target object to be cloned and merged into. This object is not modified directly as argument and returns a new object with properties from the source object merged into a deep clone of the target object. + * + * @template T - Type of the target object. + * @template S - Type of the source object. + * + * @example + * const target = { a: 1, b: { x: 1, y: 2 } }; + * const source = { b: { y: 3, z: 4 }, c: 5 }; + * + * const result = toMerged(source)(target); + * console.log(result); + * // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 } + * + * @example + * const target = { a: [1, 2], b: { x: 1 } }; + * const source = { a: [3], b: { y: 2 } }; + * + * const result = toMerged(target, source); + * console.log(result); + * // Output: { a: [3, 2], b: { x: 1, y: 2 } } + * + * @example + * const target = { a: null }; + * const source = { a: [1, 2, 3] }; + * + * const result = toMerged(target, source); + * console.log(result); + * // Output: { a: [1, 2, 3] } + */ +export function toMerged, S extends Record>( + source: S +): (target: T) => T & S; + +export function toMerged, S extends Record>( + targetOrSource: T | S, + source?: S +) { + if (source == null) { + return (target: T) => toMerged(target, targetOrSource as S); + } + + const target = targetOrSource as T; + return toMergedToolkit(target, source); +} diff --git a/tests/check-dist.spec.ts b/tests/check-dist.spec.ts index f7a7bb6df..1efe7215d 100644 --- a/tests/check-dist.spec.ts +++ b/tests/check-dist.spec.ts @@ -34,6 +34,7 @@ const ENTRYPOINTS = [ './promise', './string', './util', + './fp', ]; describe(`es-toolkit's package tarball`, () => { diff --git a/vitest.config.mts b/vitest.config.mts index c931b53bd..e639c13b0 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -15,7 +15,7 @@ export default defineConfig({ optimizer: { ssr: { enabled: true, - include: ['es-toolkit', 'es-toolkit/compat'], + include: ['es-toolkit', 'es-toolkit/compat', 'es-toolkit/fp'], }, }, }, diff --git a/yarn.lock b/yarn.lock index 8362bcce8..18ee47273 100644 --- a/yarn.lock +++ b/yarn.lock @@ -254,7 +254,17 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.24.7": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.24.2": + version: 7.24.6 + resolution: "@babel/code-frame@npm:7.24.6" + dependencies: + "@babel/highlight": "npm:^7.24.6" + picocolors: "npm:^1.0.0" + checksum: 10c0/c93c6d1763530f415218c31d07359364397f19b70026abdff766164c21ed352a931cf07f3102c5fb9e04792de319e332d68bcb1f7debef601a02197f90f9ba24 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.24.7": version: 7.24.7 resolution: "@babel/code-frame@npm:7.24.7" dependencies: @@ -357,30 +367,28 @@ __metadata: linkType: hard "@babel/helper-environment-visitor@npm:^7.22.20": - version: 7.24.7 - resolution: "@babel/helper-environment-visitor@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/36ece78882b5960e2d26abf13cf15ff5689bf7c325b10a2895a74a499e712de0d305f8d78bb382dd3c05cfba7e47ec98fe28aab5674243e0625cd38438dd0b2d + version: 7.22.20 + resolution: "@babel/helper-environment-visitor@npm:7.22.20" + checksum: 10c0/e762c2d8f5d423af89bd7ae9abe35bd4836d2eb401af868a63bbb63220c513c783e25ef001019418560b3fdc6d9a6fb67e6c0b650bcdeb3a2ac44b5c3d2bdd94 languageName: node linkType: hard "@babel/helper-function-name@npm:^7.23.0": - version: 7.24.7 - resolution: "@babel/helper-function-name@npm:7.24.7" + version: 7.23.0 + resolution: "@babel/helper-function-name@npm:7.23.0" dependencies: - "@babel/template": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/e5e41e6cf86bd0f8bf272cbb6e7c5ee0f3e9660414174435a46653efba4f2479ce03ce04abff2aa2ef9359cf057c79c06cb7b134a565ad9c0e8a50dcdc3b43c4 + "@babel/template": "npm:^7.22.15" + "@babel/types": "npm:^7.23.0" + checksum: 10c0/d771dd1f3222b120518176733c52b7cadac1c256ff49b1889dbbe5e3fed81db855b8cc4e40d949c9d3eae0e795e8229c1c8c24c0e83f27cfa6ee3766696c6428 languageName: node linkType: hard "@babel/helper-hoist-variables@npm:^7.22.5": - version: 7.24.7 - resolution: "@babel/helper-hoist-variables@npm:7.24.7" + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95 + "@babel/types": "npm:^7.22.5" + checksum: 10c0/60a3077f756a1cd9f14eb89f0037f487d81ede2b7cfe652ea6869cd4ec4c782b0fb1de01b8494b9a2d2050e3d154d7d5ad3be24806790acfb8cbe2073bf1e208 languageName: node linkType: hard @@ -476,6 +484,20 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/helper-string-parser@npm:7.24.1" + checksum: 10c0/2f9bfcf8d2f9f083785df0501dbab92770111ece2f90d120352fda6dd2a7d47db11b807d111e6f32aa1ba6d763fe2dc6603d153068d672a5d0ad33ca802632b2 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-string-parser@npm:7.24.8" + checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.25.7": version: 7.25.7 resolution: "@babel/helper-string-parser@npm:7.25.7" @@ -483,13 +505,27 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.25.7": +"@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.25.7": version: 7.25.7 resolution: "@babel/helper-validator-identifier@npm:7.25.7" checksum: 10c0/07438e5bf01ab2882a15027fdf39ac3b0ba1b251774a5130917907014684e2f70fef8fd620137ca062c4c4eedc388508d2ea7a3a7d9936a32785f4fe116c68c0 languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.24.5, @babel/helper-validator-identifier@npm:^7.24.6": + version: 7.24.6 + resolution: "@babel/helper-validator-identifier@npm:7.24.6" + checksum: 10c0/d29d2e3fca66c31867a009014169b93f7bc21c8fc1dd7d0b9d85d7a4000670526ff2222d966febb75a6e12f9859a31d1e75b558984e28ecb69651314dd0a6fd1 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-validator-option@npm:7.24.8" @@ -507,6 +543,18 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.24.6": + version: 7.24.6 + resolution: "@babel/highlight@npm:7.24.6" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.24.6" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/5bbc31695e5d44e97feb267f7aaf4c52908560d184ffeb2e2e57aae058d40125592931883889413e19def3326895ddb41ff45e090fa90b459d8c294b4ffc238c + languageName: node + linkType: hard + "@babel/highlight@npm:^7.24.7": version: 7.24.7 resolution: "@babel/highlight@npm:7.24.7" @@ -519,7 +567,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.20.5, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.6, @babel/parser@npm:^7.25.7": +"@babel/parser@npm:^7.20.5, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.7": version: 7.25.7 resolution: "@babel/parser@npm:7.25.7" dependencies: @@ -530,6 +578,37 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.0": + version: 7.24.5 + resolution: "@babel/parser@npm:7.24.5" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/8333a6ad5328bad34fa0e12bcee147c3345ea9a438c0909e7c68c6cfbea43c464834ffd7eabd1cbc1c62df0a558e22ffade9f5b29440833ba7b33d96a71f88c0 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.24.7": + version: 7.25.3 + resolution: "@babel/parser@npm:7.25.3" + dependencies: + "@babel/types": "npm:^7.25.2" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/874b01349aedb805d6694f867a752fdc7469778fad76aca4548d2cc6ce96087c3ba5fb917a6f8d05d2d1a74aae309b5f50f1a4dba035f5a2c9fcfe6e106d2c4e + languageName: node + linkType: hard + +"@babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.6": + version: 7.25.6 + resolution: "@babel/parser@npm:7.25.6" + dependencies: + "@babel/types": "npm:^7.25.6" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/f88a0e895dbb096fd37c4527ea97d12b5fc013720602580a941ac3a339698872f0c911e318c292b184c36b5fbe23b612f05aff9d24071bc847c7b1c21552c41d + languageName: node + linkType: hard + "@babel/plugin-syntax-flow@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-flow@npm:7.24.7" @@ -726,7 +805,18 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0": +"@babel/template@npm:^7.22.15": + version: 7.24.0 + resolution: "@babel/template@npm:7.24.0" + dependencies: + "@babel/code-frame": "npm:^7.23.5" + "@babel/parser": "npm:^7.24.0" + "@babel/types": "npm:^7.24.0" + checksum: 10c0/9d3dd8d22fe1c36bc3bdef6118af1f4b030aaf6d7d2619f5da203efa818a2185d717523486c111de8d99a8649ddf4bbf6b2a7a64962d8411cf6a8fa89f010e54 + languageName: node + linkType: hard + +"@babel/template@npm:^7.25.0": version: 7.25.0 resolution: "@babel/template@npm:7.25.0" dependencies: @@ -780,7 +870,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.17.0, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6, @babel/types@npm:^7.25.7": +"@babel/types@npm:^7.17.0, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.7": version: 7.25.7 resolution: "@babel/types@npm:7.25.7" dependencies: @@ -791,6 +881,39 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.24.0, @babel/types@npm:^7.8.3": + version: 7.24.5 + resolution: "@babel/types@npm:7.24.5" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.1" + "@babel/helper-validator-identifier": "npm:^7.24.5" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/e1284eb046c5e0451b80220d1200e2327e0a8544a2fe45bb62c952e5fdef7099c603d2336b17b6eac3cc046b7a69bfbce67fe56e1c0ea48cd37c65cb88638f2a + languageName: node + linkType: hard + +"@babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.6": + version: 7.25.6 + resolution: "@babel/types@npm:7.25.6" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.8" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/89d45fbee24e27a05dca2d08300a26b905bd384a480448823f6723c72d3a30327c517476389b7280ce8cb9a2c48ef8f47da7f9f6d326faf6f53fd6b68237bdc4 + languageName: node + linkType: hard + +"@babel/types@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/types@npm:7.25.2" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.8" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/e489435856be239f8cc1120c90a197e4c2865385121908e5edb7223cfdff3768cba18f489adfe0c26955d9e7bbb1fb10625bc2517505908ceb0af848989bd864 + languageName: node + linkType: hard + "@changesets/apply-release-plan@npm:^7.0.3": version: 7.0.3 resolution: "@changesets/apply-release-plan@npm:7.0.3" @@ -2352,13 +2475,20 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:*, @types/lodash@npm:^4": +"@types/lodash@npm:*": version: 4.17.7 resolution: "@types/lodash@npm:4.17.7" checksum: 10c0/40c965b5ffdcf7ff5c9105307ee08b782da228c01b5c0529122c554c64f6b7168fc8f11dc79aa7bae4e67e17efafaba685dc3a47e294dbf52a65ed2b67100561 languageName: node linkType: hard +"@types/lodash@npm:^4": + version: 4.17.4 + resolution: "@types/lodash@npm:4.17.4" + checksum: 10c0/0124c64cb9fe7a0f78b6777955abd05ef0d97844d49118652eae45f8fa57bfb7f5a7a9bccc0b5a84c0a6dc09631042e4590cb665acb9d58dfd5e6543c75341ec + languageName: node + linkType: hard + "@types/markdown-it@npm:^14.1.1": version: 14.1.2 resolution: "@types/markdown-it@npm:14.1.2" @@ -2406,6 +2536,15 @@ __metadata: languageName: node linkType: hard +"@types/ramda@npm:^0": + version: 0.30.2 + resolution: "@types/ramda@npm:0.30.2" + dependencies: + types-ramda: "npm:^0.30.1" + checksum: 10c0/dda95008860f594eb7b4fd9819adeb2dcd4d4e2baca6cb33f692f6f8ea76d04a7fd81f9a057c5c67555612769e5592cb15f91de6c9f8b619b8b1d806d19dc9ea + languageName: node + linkType: hard + "@types/semver@npm:^7.5.0": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" @@ -3279,7 +3418,7 @@ __metadata: languageName: node linkType: hard -"ast-types@npm:0.14.2, ast-types@npm:^0.14.1": +"ast-types@npm:0.14.2, ast-types@npm:^0.14.1, ast-types@npm:^0.14.2": version: 0.14.2 resolution: "ast-types@npm:0.14.2" dependencies: @@ -3356,10 +3495,12 @@ __metadata: dependencies: "@types/lodash": "npm:^4" "@types/lodash-es": "npm:^4" + "@types/ramda": "npm:^0" es-toolkit: "workspace:^" esbuild: "npm:0.23.0" lodash: "npm:^4.17.21" lodash-es: "npm:^4.17.21" + ramda: "npm:^0.30.1" rfdc: "npm:^1.4.1" vitest: "npm:^2.1.2" languageName: unknown @@ -4503,6 +4644,7 @@ __metadata: "@types/tar": "npm:^6.1.13" "@vitest/coverage-istanbul": "npm:^2.1.2" "@vue/compiler-sfc": "npm:^3.5.10" + ast-types: "npm:^0.14.2" broken-link-checker: "npm:^0.7.8" eslint: "npm:^9.9.0" eslint-config-prettier: "npm:^9.1.0" @@ -4981,7 +5123,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.4.0, esquery@npm:^1.5.0": +"esquery@npm:^1.4.0": version: 1.6.0 resolution: "esquery@npm:1.6.0" dependencies: @@ -4990,6 +5132,15 @@ __metadata: languageName: node linkType: hard +"esquery@npm:^1.5.0": + version: 1.5.0 + resolution: "esquery@npm:1.5.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/a084bd049d954cc88ac69df30534043fb2aee5555b56246493f42f27d1e168f00d9e5d4192e46f10290d312dc30dc7d58994d61a609c579c1219d636996f9213 + languageName: node + linkType: hard + "esrecurse@npm:^4.3.0": version: 4.3.0 resolution: "esrecurse@npm:4.3.0" @@ -6639,6 +6790,15 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + "lz-string@npm:^1.5.0": version: 1.5.0 resolution: "lz-string@npm:1.5.0" @@ -7517,13 +7677,20 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0": version: 1.1.0 resolution: "picocolors@npm:1.1.0" checksum: 10c0/86946f6032148801ef09c051c6fb13b5cf942eaf147e30ea79edb91dd32d700934edebe782a1078ff859fb2b816792e97ef4dab03d7f0b804f6b01a0df35e023 languageName: node linkType: hard +"picocolors@npm:^1.0.1": + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400 + languageName: node + linkType: hard + "picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -7737,6 +7904,13 @@ __metadata: languageName: node linkType: hard +"ramda@npm:^0.30.1": + version: 0.30.1 + resolution: "ramda@npm:0.30.1" + checksum: 10c0/3ea3e35c80e1a1b78c23de0c72d3382c3446f42052b113b851f1b7fc421e33a45ce92e7aef3c705cc6de3812a209d03417af5c264f67126cda539fd66c8bea71 + languageName: node + linkType: hard + "randombytes@npm:^2.1.0": version: 2.1.0 resolution: "randombytes@npm:2.1.0" @@ -8208,7 +8382,18 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.3.6, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3": +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": + version: 7.6.0 + resolution: "semver@npm:7.6.0" + dependencies: + lru-cache: "npm:^6.0.0" + bin: + semver: bin/semver.js + checksum: 10c0/fbfe717094ace0aa8d6332d7ef5ce727259815bd8d8815700853f4faf23aacbd7192522f0dc5af6df52ef4fa85a355ebd2f5d39f554bd028200d6cf481ab9b53 + languageName: node + linkType: hard + +"semver@npm:^7.3.6, semver@npm:^7.6.3": version: 7.6.3 resolution: "semver@npm:7.6.3" bin: @@ -9044,13 +9229,27 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.6.2, tslib@npm:^2.6.3": +"ts-toolbelt@npm:^9.6.0": + version: 9.6.0 + resolution: "ts-toolbelt@npm:9.6.0" + checksum: 10c0/838f9a2f0fe881d5065257a23b402c41315b33ff987b73db3e2b39fcb70640c4c7220e1ef118ed5676763543724fdbf4eda7b0e2c17acb667ed1401336af9f8c + languageName: node + linkType: hard + +"tslib@npm:^2.0.1": version: 2.7.0 resolution: "tslib@npm:2.7.0" checksum: 10c0/469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 languageName: node linkType: hard +"tslib@npm:^2.6.2, tslib@npm:^2.6.3": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a + languageName: node + linkType: hard + "tsx@npm:^4.19.0": version: 4.19.0 resolution: "tsx@npm:4.19.0" @@ -9196,6 +9395,15 @@ __metadata: languageName: node linkType: hard +"types-ramda@npm:^0.30.1": + version: 0.30.1 + resolution: "types-ramda@npm:0.30.1" + dependencies: + ts-toolbelt: "npm:^9.6.0" + checksum: 10c0/4a8b230ae9772e6534f65b1a154dd5604bcd1d74e27b49686337a215e83aa8fc93e49f8c49af395418d2950cb9fb9b900662077c1d4b73ff6fe4f4bcb83ab2d6 + languageName: node + linkType: hard + "typescript-eslint@npm:^8.1.0": version: 8.1.0 resolution: "typescript-eslint@npm:8.1.0"