Reputation: 14004
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?
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
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.
Upvotes: 1