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 aFn: Fn = name => `hello, ${ name }`;
rather than:
const aFn = (name: string): string => `hello, ${ name }`;
When using generic types we can write:
const j= <T>(i: T): T => i;
const jString: string = j('apple'); // √
const jNumber: number = j(7); // √
But how can I separate this type from a function expression?
type H<T> = (input: T) => T;
const h:H<*> = i => i; // --> WHAT SHOULD GO FOR '*'?
const hString: string = h('apple'); // X error
const hNumber: number = h(7); // X error
What should be used for *
? any
would work but that's not what I want.
In haskell this is a non-issue:
identity :: a -> a
identity a = a
identity "a-string" // √
identity 666 // √
See flow.org/try
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:
Try
f
doesn't type check as expected becausef
must not treatx
as aString
. 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:
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 off
andx
are compatible.Conclusion
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:
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.
So I have noticed that if I use bounded generics, it'll work:
Don't ask me WHY.