-
Notifications
You must be signed in to change notification settings - Fork 666
feat(functions): adding a functions module with pipe() #6143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3d67ed4
3a08d69
9fc723a
be5e5df
817e9d1
6b02456
0f81c00
f080ba7
df3f242
e03206b
28f449d
d388d3b
a943aeb
7d706f8
8ba6add
8bedf0b
11f3a3a
aa42c90
a99eef7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,7 @@ | |
| "./fmt", | ||
| "./front_matter", | ||
| "./fs", | ||
| "./functions", | ||
| "./html", | ||
| "./http", | ||
| "./ini", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "name": "@std/functions", | ||
| "version": "0.1.0", | ||
| "exports": { | ||
| ".": "./mod.ts", | ||
| "./pipe": "./pipe.ts" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // Copyright 2018-2025 the Deno authors. MIT license. | ||
|
|
||
| // This module is browser compatible. | ||
|
|
||
| /** | ||
| * Utilities for working with functions. | ||
| * | ||
| * ```ts | ||
| * import { pipe } from "@std/functions"; | ||
| * import { assertEquals } from "@std/assert"; | ||
| * | ||
| * const myPipe = pipe( | ||
| * Math.abs, | ||
| * Math.sqrt, | ||
| * Math.floor, | ||
| * (num: number) => `result: ${num}`, | ||
| * ); | ||
| * assertEquals(myPipe(-2), "result: 1"); | ||
| * ``` | ||
| * | ||
| * @module | ||
| */ | ||
| export * from "./pipe.ts"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| // Copyright 2018-2025 the Deno authors. MIT license. | ||
|
|
||
| // deno-lint-ignore-file no-explicit-any | ||
| type AnyFunc = (...arg: any) => any; | ||
|
|
||
| type LastFnReturnType<F extends Array<AnyFunc>, 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 AnyFunc[], Acc extends AnyFunc[] = []> = 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<Tail, [...Acc, (...args: A) => B]> | ||
| : Acc | ||
| : Acc; | ||
|
|
||
| /** | ||
| * Composes functions from left to right, the output of each function is the input for the next. | ||
| * | ||
| * @example Usage | ||
| * ```ts | ||
| * import { assertEquals } from "@std/assert"; | ||
| * import { pipe } from "@std/functions"; | ||
| * | ||
| * const myPipe = pipe( | ||
| * Math.abs, | ||
| * Math.sqrt, | ||
| * Math.floor, | ||
| * (num: number) => `result: ${num}`, | ||
| * ); | ||
| * assertEquals(myPipe(-2), "result: 1"); | ||
| * ``` | ||
| * | ||
| * @param input The functions to be composed | ||
| * @returns A function composed of the input functions, from left to right | ||
| */ | ||
| export function pipe(): <T>(arg: T) => T; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this first overload? What is this for?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is my habit that if an edge case has a natural interpretation I include it by default, even without a concrete use-case. This isn't a strong opinion, happy to remove this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would note that a scenario for this will, of course, involve |
||
| export function pipe< | ||
| Fn1 extends AnyFunc, | ||
| Fn2 extends (arg: ReturnType<Fn1>) => any, | ||
| >( | ||
| firstFunction: Fn1, | ||
| function2: Fn2, | ||
| ): (...args: Parameters<Fn1>) => ReturnType<Fn2>; | ||
|
|
||
| export function pipe< | ||
| Fn1 extends AnyFunc, | ||
| Fn2 extends (arg: ReturnType<Fn1>) => any, | ||
| Fn3 extends (arg: ReturnType<Fn2>) => any, | ||
| >( | ||
| firstFunction: Fn1, | ||
| function2: Fn2, | ||
| function3: Fn3, | ||
| ): (...args: Parameters<Fn1>) => ReturnType<Fn3>; | ||
|
|
||
| export function pipe< | ||
| Fn1 extends AnyFunc, | ||
| Fn2 extends (arg: ReturnType<Fn1>) => any, | ||
| Fn3 extends (arg: ReturnType<Fn2>) => any, | ||
| Fn4 extends (arg: ReturnType<Fn3>) => any, | ||
| >( | ||
| firstFunction: Fn1, | ||
| function2: Fn2, | ||
| function3: Fn3, | ||
| function4: Fn4, | ||
| ): (...args: Parameters<Fn1>) => ReturnType<Fn4>; | ||
|
|
||
| export function pipe< | ||
| Fn1 extends AnyFunc, | ||
| Fn2 extends (arg: ReturnType<Fn1>) => any, | ||
| Fn3 extends (arg: ReturnType<Fn2>) => any, | ||
| Fn4 extends (arg: ReturnType<Fn3>) => any, | ||
| Fn5 extends (arg: ReturnType<Fn4>) => any, | ||
| >( | ||
| firstFunction: Fn1, | ||
| function2: Fn2, | ||
| function3: Fn3, | ||
| function4: Fn4, | ||
| function5: Fn5, | ||
| ): (...args: Parameters<Fn1>) => ReturnType<Fn5>; | ||
|
|
||
| export function pipe< | ||
| Fn1 extends AnyFunc, | ||
| Fn2 extends (arg: ReturnType<Fn1>) => any, | ||
| Fn3 extends (arg: ReturnType<Fn2>) => any, | ||
| Fn4 extends (arg: ReturnType<Fn3>) => any, | ||
| Fn5 extends (arg: ReturnType<Fn4>) => any, | ||
| Fn6 extends (arg: ReturnType<Fn5>) => any, | ||
| >( | ||
| firstFunction: Fn1, | ||
| function2: Fn2, | ||
| function3: Fn3, | ||
| function4: Fn4, | ||
| function5: Fn5, | ||
| function6: Fn6, | ||
| ): (...args: Parameters<Fn1>) => ReturnType<Fn6>; | ||
|
|
||
| export function pipe< | ||
| Fn1 extends AnyFunc, | ||
| Fn2 extends (arg: ReturnType<Fn1>) => any, | ||
| Fn3 extends (arg: ReturnType<Fn2>) => any, | ||
| Fn4 extends (arg: ReturnType<Fn3>) => any, | ||
| Fn5 extends (arg: ReturnType<Fn4>) => any, | ||
| Fn6 extends (arg: ReturnType<Fn5>) => any, | ||
| Fn7 extends (arg: ReturnType<Fn6>) => any, | ||
| >( | ||
| firstFunction: Fn1, | ||
| function2: Fn2, | ||
| function3: Fn3, | ||
| function4: Fn4, | ||
| function5: Fn5, | ||
| function6: Fn6, | ||
| function7: Fn7, | ||
| ): (...args: Parameters<Fn1>) => ReturnType<Fn7>; | ||
|
|
||
| export function pipe<FirstFn extends AnyFunc, F extends AnyFunc[]>( | ||
| firstFunction: FirstFn, | ||
| ...fns: PipeArgs<F> extends F ? F : PipeArgs<F> | ||
| ): (arg: Parameters<FirstFn>[0]) => LastFnReturnType<F, ReturnType<FirstFn>>; | ||
|
|
||
| export function pipe<FirstFn extends AnyFunc, F extends AnyFunc[]>( | ||
| firstFunction?: FirstFn, | ||
| ...fns: PipeArgs<F> extends F ? F : PipeArgs<F> | ||
| ): any { | ||
| if (!firstFunction) { | ||
| return <T>(arg: T) => arg; | ||
| } | ||
|
|
||
| return (...arg: Parameters<FirstFn>) => { | ||
| return (fns as AnyFunc[]).reduce( | ||
| (acc, fn) => fn(acc), | ||
| firstFunction(...arg), | ||
| ); | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // Copyright 2018-2025 the Deno authors. MIT license. | ||
|
|
||
| import { assertEquals, assertThrows } from "@std/assert"; | ||
| import { pipe } from "./pipe.ts"; | ||
|
|
||
| Deno.test("pipe() handles mixed types", () => { | ||
| const inputPipe = pipe( | ||
| Math.abs, | ||
| (num) => `result: ${num}`, | ||
| ); | ||
| assertEquals(inputPipe(-2), "result: 2"); | ||
| }); | ||
| Deno.test("pipe() handles first function with two arguments", () => { | ||
| function add(a: number, b: number): number { | ||
| return a + b; | ||
| } | ||
| const inputPipe = pipe( | ||
| add, | ||
| (num) => `result: ${num}`, | ||
| ); | ||
| assertEquals(inputPipe(3, 2), "result: 5"); | ||
| }); | ||
|
|
||
| Deno.test("en empty pipe is the identity function", () => { | ||
| const inputPipe = pipe(); | ||
| assertEquals(inputPipe("hello"), "hello"); | ||
| }); | ||
|
|
||
| Deno.test("pipe() throws an exceptions when a function throws an exception", () => { | ||
| const inputPipe = pipe( | ||
| Math.abs, | ||
| Math.sqrt, | ||
| Math.floor, | ||
| (num: number) => { | ||
| throw new Error("This is an error for " + num); | ||
| }, | ||
| (num: number) => `result: ${num}`, | ||
| ); | ||
| assertThrows(() => inputPipe(-2)); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.