flow generic type for function [removed]arrow functions)

前端 未结 3 610
旧巷少年郎
旧巷少年郎 2021-02-07 21:48

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         


        
相关标签:
3条回答
  • 2021-02-07 21:54

    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.

    0 讨论(0)
  • 2021-02-07 22:04
     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);
    
    0 讨论(0)
  • 2021-02-07 22:04

    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.

    0 讨论(0)
提交回复
热议问题