flow generic type for function expression (arrow functions)

放肆的年华 提交于 2019-12-03 06:32:42
norbertpy

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!