Duncan Lukkenaer
Duncan Lukkenaer

Reputation: 14004

How to explicitly type a generic function in TypeScript?

Problem

I have the following generic function type:

type Validator<TInput, TOutput extends TInput> = (value: TInput) => Validated<TOutput>;

Now I want to implement this type, so I did the following:

const isNotNull: Validator<T | null, T> = <T>(value: T | null): Validated<T> => {
    // Implementation here
};

This doesn't work because T is used before it's defined.

I know I can infer the type of isNotNull but I want to explicitly declare it as Validator as a constraint. How do I do this?


Context

This Validator type will be used as a function argument, like the following:

function validate<TInput, TOutput>(
    value: TInput,
    validator: Validator<TInput, TOutput>
): TOutput {}

Of course in reality there is no need for this function. The real scenario is more complex, but I've stripped it down to the minimum.

This is why Validator must be generic.

Upvotes: 2

Views: 414

Answers (1)

jcalz
jcalz

Reputation: 330061

TypeScript lacks the expressiveness necessary to describe the isNotNull function as a type of Validator. If TypeScript had arbitrary "generic values" as described in microsoft/TypeScript#17574, one might be able to say something like:

declare const isNotNull: forall T, Validator<T | null, T>; // not valid TS, error

But there is no way to do this currently. (For more info, I go on about generics in TypeScript in the answer to this question.)

Without the ability to do this programmatically, you will probably want to do it manually:

declare const isNotNull: <T>(value: T | null) => Validated<T>;

Luckily, though, while the compiler cannot express that isNotNull is related to Validator, it can recognize it. So you can still pass isNotNull to validate() without problems:

validate(Math.random() < 0.5 ? 123 : null, isNotNull);
// function validate<number | null, number>(
//   value: number | null, validator: Validator<number | null, number>
// ): number

The compiler sees that isNotNull can be treated as a Validator<number | null, number>.


The only other way I can imagine proceeding here is to represent a generic function that returns Validator<T | null, T> when you call it with a type parameter of T, like this:

const getIsNotNull = <T>(): Validator<T | null, T> => isNotNull;
validate(Math.random() < 0.5 ? "hey" : null, getIsNotNull<"hey">());

This gives you the ability to say "isNotNull has something to do with Validator", but at the cost of a useless no-arg curried function. I'd much prefer using validate with isNotNull directly.


Playground link to code

Upvotes: 1

Related Questions