-->

flow generic type for function [removed]arrow func

2019-03-18 06:20发布

问题:

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

回答1:

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.



回答2:

 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);


回答3:

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.