TypeScript: implementing pipe function using tuple

Viewed 78

Playground Link

I am implementing a pipe function but with one difference from regular implementations: rather than taking functions as arguments, it must accept an array of functions passed as a single argument.

Like most implementations, my pipe implementation captures the input and output types of each function using type varaibles.

// simplified without overloads
type Fn<I, O> = (i: I) => O;

function pipeWithArr<A, B>(params: [Fn<any, A>, Fn<A, B>]) {
  console.log(params);
}

For comparison, common implementations like https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe would look like this:

function pipeWithArgs<A, B>(a: Fn<any, A>, b: Fn<A, B>) {
  console.log(a);
}

Although both implementations work similarly, the former is less capable at infering the input and output types variables – specifically when those types are inferred by generic functions within the array itself.

const source = <O>(data: O): Fn<any, O> => () => data;
const transform = <I, O>(fn: (i: I) => O) => (i: I) => fn(i);

// Success!
// A and B inferred as desired – pipeWithArgs<A = { a: number }, B = number>
pipeWithArgs(
  source({ a: 1 }),
  transform((result) => result.a)
);

// Error - Property 'a' does not exist on type 'unknown'
// A and B typed as unknown – pipeWithTuple<A = unknown, B = unknown>
pipeWithArr([source({ a: 1 }), transform((result) => result.a)]);

We can help the compiler to infer A and B:

// By not using generic functions
pipeWithArr([() => ({ a: 1 }), transform((result) => result.a)]);

// Or explictly typing the generics
pipeWithArr([
  source<{ a: number }>({ a: 1 }),
  transform((result) => result.a),
]);

However, it is desired that the user of pipeWithArr can: a) be able to pass it generic functions b) not need to provide explicit types

Are there any workarounds to make pipeWithArr work like pipeWithArgs or is this a limitation of the compiler? Or, a misunderstanding of how it should work?

1 Answers
Related