Add compose function and extend pipe with overloading

The best way to get type saftey with composition functions is via
overloading. This does impose a hard limit on the humber of functions
that can be composed. In this case, the limit is 10.
This commit is contained in:
Eric Rumsey 2025-05-27 16:58:20 -05:00
parent e696e5d9d2
commit 300a538314

View File

@ -1,11 +1,171 @@
import { type Either, left, right } from './either' import { type Either, left, right } from './either'
import { none, type Option, some } from './option' import { none, type Option, some } from './option'
// Pipe function for composition interface Pipe {
export const pipe = <A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C => <A>(value: A): A
bc(ab(a)) <A, B>(value: A, fn1: (a: A) => B): B
<A, B, C>(value: A, fn1: (a: A) => B, fn2: (b: B) => C): C
<A, B, C, D>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
): D
<A, B, C, D, E>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
fn4: (d: D) => E,
): E
<A, B, C, D, E, F>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
fn4: (d: D) => E,
fn5: (e: E) => F,
): F
<A, B, C, D, E, F, G>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
fn4: (d: D) => E,
fn5: (e: E) => F,
fn6: (f: F) => G,
): G
<A, B, C, D, E, F, G, H>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
fn4: (d: D) => E,
fn5: (e: E) => F,
fn6: (f: F) => G,
fn7: (g: G) => H,
): H
<A, B, C, D, E, F, G, H, I>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
fn4: (d: D) => E,
fn5: (e: E) => F,
fn6: (f: F) => G,
fn7: (g: G) => H,
fn8: (h: H) => I,
): I
<A, B, C, D, E, F, G, H, I, J>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
fn4: (d: D) => E,
fn5: (e: E) => F,
fn6: (f: F) => G,
fn7: (g: G) => H,
fn8: (h: H) => I,
fn9: (i: I) => J,
): J
<A, B, C, D, E, F, G, H, I, J, K>(
value: A,
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
fn4: (d: D) => E,
fn5: (e: E) => F,
fn6: (f: F) => G,
fn7: (g: G) => H,
fn8: (h: H) => I,
fn9: (i: I) => J,
fn10: (j: J) => K,
): K
}
export const pipe: Pipe = (value: unknown, ...fns: Function[]): unknown => {
return fns.reduce((acc, fn) => fn(acc), value)
}
// Compose Function (reverse order)
interface Compose {
<A>(): (a: A) => A
<A, B>(fn1: (a: A) => B): (a: A) => B
<A, B, C>(fn2: (b: B) => C, fn1: (a: A) => B): (a: A) => C
<A, B, C, D>(
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => D
<A, B, C, D, E>(
fn4: (d: D) => E,
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => E
<A, B, C, D, E, F>(
fn5: (e: E) => F,
fn4: (d: D) => E,
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => F
<A, B, C, D, E, F, G>(
fn6: (f: F) => G,
fn5: (e: E) => F,
fn4: (d: D) => E,
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => G
<A, B, C, D, E, F, G, H>(
fn7: (g: G) => H,
fn6: (f: F) => G,
fn5: (e: E) => F,
fn4: (d: D) => E,
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => H
<A, B, C, D, E, F, G, H, I>(
fn8: (h: H) => I,
fn7: (g: G) => H,
fn6: (f: F) => G,
fn5: (e: E) => F,
fn4: (d: D) => E,
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => I
<A, B, C, D, E, F, G, H, I, J>(
fn9: (i: I) => J,
fn8: (h: H) => I,
fn7: (g: G) => H,
fn6: (f: F) => G,
fn5: (e: E) => F,
fn4: (d: D) => E,
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => J
<A, B, C, D, E, F, G, H, I, J, K>(
fn10: (j: J) => K,
fn9: (i: I) => J,
fn8: (h: H) => I,
fn7: (g: G) => H,
fn6: (f: F) => G,
fn5: (e: E) => F,
fn4: (d: D) => E,
fn3: (c: C) => D,
fn2: (b: B) => C,
fn1: (a: A) => B,
): (a: A) => K
}
export const compose: Compose = (...fns: Function[]) => {
return (value: unknown) => fns.reduceRight((acc, fn) => fn(acc), value)
}
// TryCatch helper using Either
export const tryCatch = <A>(f: () => A): Either<A, Error> => { export const tryCatch = <A>(f: () => A): Either<A, Error> => {
try { try {
return right(f()) return right(f())
@ -14,6 +174,5 @@ export const tryCatch = <A>(f: () => A): Either<A, Error> => {
} }
} }
// Option from nullable
export const fromNullable = <A>(a: A | null | undefined): Option<A> => export const fromNullable = <A>(a: A | null | undefined): Option<A> =>
a == null ? none : some(a) a == null ? none : some(a)