Obviously, this is fairly trivial using the any
keyword:
type SingleParamFunction = (arg: any) => any;
const foo: SingleParamFunction = (x: number): number => x + 1;
I've tried the following:
type SingleParamFunction = (arg: unknown) => unknown;
const foo: SingleParamFunction = (x: number): number => x + 1;
However, the compiler complains that 'unknown' is not assignable to type 'number'.
I'm curious as to whether it is possible to do so without resorting to any
, perhaps a solution using generics.
A potential use case might look something like the following:
interface Foo<I, O> {
input: I;
transform: (x: I) => O;
}
const foos: Foo<any, any>[] = [
{
input: 1,
transform: (x: number) => x + 1,
},
{
input: 'hello',
transform: (s: string) => s + ' world',
},
];
for (const foo of foos) {
console.log(foo.transform(foo.input));
}
There are several solutions to your problem that are equally valid but progressively enhance the type safety.
The first one is the trivial case you presented: (x: any) => any
. Despite the "never use any" rule of thumb, a judgemental use of any
can be a valid choice. If all you need is to tell the compiler that the function accepts a single argument of any type , then why not?
The second one is more sound and was already proposed by fjc in the comment :
Why not use a generic like this: type SingleParamFunction = (arg: T) => T; const foo: SingleParamFunction = (x) => x + 1;?
This solves the contravariance issue, but the compiler will complain about the signatures of the transform
methods because the union will be passed through, so the x
and the return type will be inferred as a union (for example, string | number
):
interface Foo<I> {
input: I;
transform: SingleParamFunction<I>;
}
const foos: Foo<string | number>[] = [
{
input: 1,
transform: (x) => x + 1, //Type 'string' is not assignable to type 'number'.(2322)
},
{
input: 'hello',
transform: (s: string) => s + ' world', //Type 'string | number' is not assignable to type 'string'.
},
];
for (const foo of foos) {
console.log(foo.transform(foo.input));
}
This is, however, not the solution's problem. Because foos
is typed as a mutable array, the compiler does not know what the type of input
or transform
's x
and return type in a given member is, only that it should be string | number
string | number
.
If you remove the type annotation, you will see, however, that the type of the array is correctly inferred. This should give you a hint of what you need to check for and leads us to a third solution:
({
input: number;
transform: (x: number) => number;
} | {
input: string;
transform: (s: string) => string;
})[]
So, how would you do it? Mapped types to the rescue! You need to define an "array of Foo's where for each type in possible input
types there possibly is a member that is a Foo
of this type". Here is how such a type could look like (with a help of key remapping ):
type validator<I> = Array<{ [T in I as number]: Foo<T> }[number]>;
And voila:
//all ok, valid assignment
const bars: validator<string | number> = [
{
input: 1,
transform: (x: number) => x + 1
},
{
input: 'hello',
transform: (s: string) => s + ' world'
},
];
const invalid : validator<string|number> = [
{
input: "nope", //Type 'string' is not assignable to type 'number'.(2322)
transform: (x: number) => x + 1
},
{
input: 'hello',
transform: (s: string) => Infinity //Type 'number' is not assignable to type 'string'.(2322)
},
];
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.