0

Here's what I've done so far to implement function composition sequence using Array.reduce.

type Compose = <A, B, C>
  (f: (a: A) => B, g: (b: B) => C) =>
  (c: A) => C;
const compose: Compose =
  (f, g) => a => g(f(a));

type C_ =
  (fs: Array<(a: any) => any>) =>
    (c: unknown) => unknown
const c_: C_ =
  fs => fs.reduce(compose);

  //-------------------------------
  const f = (a: number) => a * 2;
  const g = (a: number) => a + 1;
  const h = (a: number) => a * 5;
  const s = (a: number) => a.toString();
  //-------------------------------

  const composedF = c_([f, g, h, s]);
  console.log(
    composedF(1)
  ); // "15"

Playground Link

I needed to write

type C_ =
  (fs: Array<(a: any) => any>) =>
    (c: unknown) => unknown

with any type and if changed to unknown for instance,

type C_ =
  (fs: Array<(a: unknown) => unknown>) =>
    (c: unknown) => unknown

I have an error:

Type '(a: number) => number' is not assignable to type '(a: unknown) => unknown'.
  Types of parameters 'a' and 'a' are incompatible.
    Type 'unknown' is not assignable to type 'number'.

So, my question is, is there any way not to use any in this code?

Edit:

Based on @jcalz work, now I tried to implement p_ as pipeline application on the composition of functions.

I thought it's trivial, but for some unknown reason, the type doesn't work and allows the illegal operation against my expectation.

type compose = <A, B, C>
  (f: (a: A) => B, g: (b: B) => C) =>
  (c: A) => C;
const compose: compose =
  (f, g) => a => g(f(a));

type Lookup<T, K extends keyof any, Else = never> = K extends keyof T ? T[K] : Else
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never;
type Func1 = (a: any) => any;
type aType<F, Else = never> = F extends (a: infer A) => any ? A : Else;
type AsChain<F extends [Func1, ...Func1[]], G extends Func1[] = Tail<F>> =
  { [K in keyof F]: (a: aType<F[K]>) => aType<Lookup<G, K, any>, any> };
type Last<T extends any[]> = T extends [...infer F, infer L] ? L : never;
type LaxReturnType<F> = F extends (...as: any) => infer R ? R : never;

type c_ = <F extends
  [(a: any) => any, ...Array<(a: any) => any>]>
  (fs: F & AsChain<F>) =>
  (a: aType<F[0]>) => LaxReturnType<Last<F>>;
const c_: c_ = fs => fs.reduce(compose);

type p_ = <F extends
  [(a: any) => any, ...Array<(a: any) => any>]>
  (a: aType<(F & AsChain<F, Tail<F>>)[0], never>) =>
  (fs: F & AsChain<F>) => LaxReturnType<Last<F & AsChain<F, Tail<F>>>>;
const p_: p_ = a => fs => {
  const composedF = c_(fs)
  return composedF(a)
};

//-------------------------------
const f = (a: number) => a * 2;
const g = (a: number) => a + 1;
const h = (a: number) => a * 5;
const s = (a: number) => a.toString();
//-------------------------------

const composedF = c_([f, g, h, s]);
console.log(
  composedF(1)
); // "15"

console.log(
  p_(1)([f, s, g, h, s])
); // type error expected, but illegaly works as "105"
 

Playground Link

sailsky
  • 325
  • 1
  • 9
  • This is not trivial ... there's a healthy discussion about it here: https://dev.to/ascorbic/creating-a-typed-compose-function-in-typescript-3-351i (the proposed solution isn't complete) Just an observation: a truly type-safe `compose` function would allow `[f, g, h, s].reduce(compose)` but would give you an error on `[f, g, s, h].reduce(compose)`. It does seem like a tall order. – geofh Mar 13 '22 at 17:25
  • @geofh Thanks for your pointer. It looks like so. Appreciated to study more. – sailsky Mar 13 '22 at 17:48
  • 2
    You can't get the compiler to see that reduce behaves the way you want, you'll need type assertions. And the type of `c_` is complicated to represent. The closest I've ever gotten is [this approach](//tsplay.dev/m0oZRN). As such I'd say this question is very similar to [this one](https://stackoverflow.com/q/53173203/2887218). Let me know if you agree/disagree about the similarity; if you agree I'll probably close this as a duplicate. If you disagree you should probably [edit] this question to explicitly highlight the differences that make the other answer inappropriate here. – jcalz Mar 13 '22 at 18:34
  • @jcalz Thanks a lot for your comprehensive answer! Since you kindly have provided the complete code corresponding to this question, I would be rather comfortable if you copy-paster your comment and the code then I could mark as the accepted answer here. Possible duplicate pointer will help to future readers, of course. But again, you provided the direct answer and I want to check the mark as the answer here. Thanks again. – sailsky Mar 13 '22 at 18:54
  • @jcalz Please see my edit. Based on your work, now I tried to implement `p_` as pipeline application on the composition of functions. I thought it's trivial, but for some unknown reason, the type doesn't work and allows the illegal operation against my expectation. Any idea? Thanks. – sailsky Mar 14 '22 at 00:05

0 Answers0