Home > Software engineering >  How to type for an optional transform
How to type for an optional transform

Time:03-15

I think code is the best way to explain what I'm trying to do:

interface Input { in: string }
interface Output { out: string }

function doStuff(input: Input): Output {
    return { out: input.in };
}

function f<Out>(input: Input, transform?: (data: Output) => Out) /* result-type ? */ {
    const output = doStuff(input);
    if(transform) return transform(output);
    else return output;
}
f({in: 'simple'}).out == 'simple';
f({in: 'with transform'}, (data: Output) => data.out) == 'with transform';

I'm struggling to get the return type of the function f right. Basically I want f to return Output when transform is undefined and the result type of transform when it is given.

I tried using different declarations for both cases, and conditional types, but there always is some issue. Can this be modeled with typescript ?

CodePudding user response:

I tried using different declarations for both cases, and conditional types, but there always is some issue. Can this be modeled with typescript?

By "different declarations", I presume that you mean function overloads (which work for your case).

This cannot currently be modeled using conditional types, because TypeScript is not yet capable of inferring type information from optional parameters which are constrained generics. But if it could, your conditional version would look something like this (doesn't work!):

TS Playground

type Transform<I, O> = (data: I) => O;
type Input<T> = { in: T };
type Output<T> = { out: T };

function doStuff <T>(input: Input<T>): Output<T> {
  return { out: input.in };
}

function f <T, Fn extends Transform<Output<T>, any>>(
  input: Input<T>,
  ...[transform]: [] | [transform: Fn]
): typeof transform extends undefined ? Output<T> : ReturnType<Fn> {
  const output = doStuff(input);
  return transform ? transform(output) : output;
}

f({in: 'simple'}).out === 'simple'; // any (incorrectly inferred from the generic constraint)
f({in: 'with transform'}, data => data.out) === 'with transform'; // string (correctly inferred)

CodePudding user response:

Two overloads do the trick:

function f(input: Input): Output;
function f<T extends (data: Output) => unknown>(input: Input, transform: T): ReturnType<T>;
function f<T extends ((data: Output) => unknown) | undefined>(input: Input, transform?: T)  {
    const output = doStuff(input);
    if(transform) return transform(output);
    else return output;
}

Playground

  • Related