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
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.
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:
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.