I usually try to keep flow function types separate from their implementation. It\'s a slightly more readable when I write:
type Fn = string => string;
const a
So I have noticed that if I use bounded generics, it'll work:
type H<T> = <T: *>(input: T) => T;
const h:H<*> = i => i;
const a: string = h('apple'); // √
const b: number = h(7); // √
const c: {} = h({ nane: 'jon' }); // √
Don't ask me WHY.
type H<T> = (input: T) => T;
const h2:H<*> = i => i;
const h3:H<*> = i => i;
const hString: string = h3('apple');
const hNumber: number = h2(7);
I think the underlying issue with your code and type definitions is based on a misunderstanding of a particular property of generics (aka parametric polymorphism): Parametricity.
Parametricity states that a generic function must not know anything about the types of its polymorphic arguments/return value. It is type agnostic.
As a result, a generic function must treat each value associated with a polymorphic type equally, regardless of the concrete type. This is pretty limiting, of course. When a function doesn't know anything about its argument, it can't do anything with it other than returning it unchanged.
Let's have a look at an incorrect generic function:
const f = <a>(x :a) :a => x + "bar"; // type error
Try
f
doesn't type check as expected because f
must not treat x
as a String
. Flow enforces parametricity.
Please note that generics are much more useful in connection with higher order functions. Here is an example of a correct generic higher order function:
type F<X, Y> = (x: X) => Y;
const apply = <X, Y>(f: F<X, Y>, x: X) :Y => f(x);
const sqr = (x :number) :number => x * x;
const toUC = (s :string) :string => s.toUpperCase();
apply(sqr, 5); // does type check
apply(toUC, "foo"); // does type check
Try
Why would you define a specific version of apply
for every possible type? Just apply it to values of arbitrary type provided that the types of f
and x
are compatible.
When you have an unbounded generic function, you can apply it to whatever value you want - it always works as expected. So there isn't really a reason to declare a distinct function for each possible type. Smart people have invented polymorphism in order to avoid exactly this.
A problem remains, though. As soon as you separate the generic type definition form the function declaration, Flow no longer enforces parametricity:
const f = <a>(x :a) :a => x + "bar"; // type error
type G<a> = a => a;
const g :G<*> = x => x + ""; // type checks!?!
Try
So your question is still reasonable and unfortunately, I can't offer you a recipe. Hopefully, Flow's behaviour will change in future versions.
In your own answer, you suggest using bounded generics. I wouldn't do that because it solves a problem that doesn't exist at all and because it seems to be a misuse of this sort of polymorphism.