Reputation: 1304
So in this basic example (tryflow):
// basic identity function example with generic type
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function identity<T>(same: T): T {
return same;
}
// here identity acts as (Foo) => Foo
const foo2: Foo = identity(foo);
// and here it's (Bar) => Bar
const bar2: Bar = identity(bar);
My identity
function, using generics, takes whatever type is given to it. As arguments are bound to it, T
becomes first Foo
and then Bar
.
What I want is a higher-order function which returns a generic function. I can write a higher-order function which uses generics (tryflow):
type IdentityFunction = <T>(self: T) => T;
// error here
const baseId: IdentityFunction = (same) => same;
// ^ Cannot assign function to
// `baseId` because `T` [1] is
// incompatible with `T` [2] in
// the return value.
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function makeIdentity(func: IdentityFunction): IdentityFunction {
return func;
}
const identity: IdentityFunction = makeIdentity(baseId);
const foo2: Foo = identity(foo);
const bar2: Bar = identity(bar);
For me, this approach makes the most sense. I'm honestly not sure why I get this error. How can T
be incompatible with itself? Is it because a type is never explicitly applied to T
? It's somehow indeterminate so it just can't be used for anything? But then, isn't that the whole point of generics? Anyway, I'm sure I'm just missing some subtle point of the type system, or maybe I'm going about this the wrong way. Any help appreciated.
Upvotes: 1
Views: 133
Reputation: 3478
You need to generically type your baseID function so Flow knows what you expect as argument and return type. It seems like Flow doesn't use the type of IndentityFunction when trying to figure out what the baseId function is really doing.
(Try)
type IdentityFunction = <T>(self: T) => T;
// no more error
const baseId: IdentityFunction = <S>(same: S): S => same;
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function makeIdentity(func: IdentityFunction): IdentityFunction {
return func;
}
const identity: IdentityFunction = makeIdentity(baseId);
const foo2: Foo = identity(foo);
const bar2: Bar = identity(bar);
You can simplify the instantiation of baseId to:
const baseId = <S>(same: S): S => same;
And flow still understands what's going on here.
This behavior is a little confusing and I wonder if there is a good reason for it. You would think that it could take what's on the lefthand side and apply it to the function on the right (especially in simple cases like this one). Maybe it has to do with how flow sees the righthand expression? If anyone else has an idea, I'd love to hear it.
Either way, I tend to avoid declaring the type of functions on the lefthand side of declarations. Not as a rule, I just rarely want to declare the type of a function somewhere besides the function itself.
Upvotes: 1