镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

question: how can i create and return a generic function in TypeScript? #9949

Description

function flip<a, b, c>(fn: (one: a, two: b) => c): (one: b, two: a) => c {
    return function flipped(one: b, two: a): c {
        return fn(two, one);
    };
}
function of2<a, b>(one: a, two: b): [a, b] {
    return [one, two];
}
const flipped = flip(of2); // expected: <a, b>(one: b, two: a) => [a, b], actual: (one: {}, two: {}) => [{}, {}]

Activity

  1. jesseschalken commented on Jul 26, 2016

    @jesseschalken
    Contributor

    When TS sees flip(of2), it has to unify (one: a1, two: b1) => c1 (from flip) with <a2, b2>(one: a2, two: b2): [a2, b2] (from of2). My guess is at this step it sees that of2 has type parameters but no information for them, so just fills them in with {}.

    What you would need is for TS to lift the type parameters up a level rather than just defaulting them. That is, if any type parameters needed to be defaulted when inferring the type of an expression (eg flip(of2)), the resulting type should itself have those parameters. (More than one set of type parameters may have needed to be filled in, so their order may be arbitrary.)

    I'm not sure what the broad implications of that would be, though.

  2. added
    QuestionAn issue which isn't directly actionable in code
    on Jul 27, 2016
  3. added
    SuggestionAn idea for TypeScript
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    and removed
    QuestionAn issue which isn't directly actionable in code
    on Sep 20, 2016
  4. gcnew commented on Dec 16, 2016

    @gcnew
    Contributor

    I took a stab at this and made a hacky implementation. It's by no means complete, but it can be used to play around with.

    Current progress:

    function flip<A, B, R>(f: (a: A, b: B) => R): (b: B, a: A) => R {
        return (b, a) => f(a, b);
    }
    
    function id<T>(x: T): T {
        return x;
    }
    
    function fconst<X, Y>(x: X, y: Y): X {
        return x;
    }
    
    function addStr(x: number, y: string) {
        return x + y;
    }
    
    function tagged<T extends string, Q>(tag: T, value: Q): { tag: T, value: Q } {
        return { tag, value };
    }
    
    // it was working before
    const f1 = flip(addStr); // (b: string, a: number) => string
    const v1 = f1("hello", 3);
    const v2 = id(id)(3); // `3`
    
    // working now
    const f2 = flip(id);     // <T>(b: {}, a: T): T
    const f3 = flip(fconst); // <Y, X>(b: Y, a: X) => X
    
    const v3 = f3(1, "qw") // `"qw"`
    const v4 = f3([], {})  // `{}`
    
    const f4 = flip(tagged);   // <Q, T extends string>(b: Q, a: T) => { tag: T, value: Q }
    const v5 = f4(5, "hello"); // { tag: "hello", value: number }
    const v6 = f4(5, 5);       // Error as expected
    
    declare function compose<A, B, C>(f: (b: B) => C, g: (a: A) => B): (a: A) => C;
    
    declare const f: <T>(x:number) => T;
    declare const g: (x:boolean) => number;
    const f5 = compose(f, g)      // OUCH! this gets type `<T>(a: boolean) => T`
    
    declare const g_2: <T>(x: T) => boolean;
    declare const f_2: (x: boolean) => number;
    const f6 = compose(f_2, g_2)  // <T> (a: T) => number
    const f7 = compose(id, x => String(x)) // (a: {}) => string
    
    // Was working, now not:
    declare function h<R>(f: (x: number) => R): R;
    var z: number = h(id);  // number ~ T, R ~ T
                            // backpropagation is now broken
    
    const arr = [1, 2, 3].map(id); // T[]     Snap :(

    See https://github.com/gcnew/TypeScript/tree/polyFuncUnification

    Edit: updated branch link and the examples

  5. gcnew commented on Dec 16, 2016

    @gcnew
    Contributor

    Mohamed Hegazy (@mhegazy) What should the proposal look like?

  6. mhegazy commented on Dec 16, 2016

    @mhegazy
    Contributor

    mainly how it would be implemented. implications on different parts of the system, e.g. assignablity (isTypeRelatedTo), overload resolution, and generic argument inference.

  7. jesseschalken commented on Dec 19, 2016

    @jesseschalken
    Contributor
  8. KiaraGrouwstra commented on Dec 20, 2016

    @KiaraGrouwstra
    Contributor

    Man, this is already an awesome improvement as-is, hope this could make it in.

    Marin Marinov (@gcnew): what would you be expecting for f5 here? I'd be inclined to think the compiler is getting the best inference it can using the info available...

    That Type argument candidate 'T' is not a valid type argument because it is not a supertype of candidate 'string'. in f7 is pretty weird though.

  9. gcnew commented on Dec 20, 2016

    @gcnew
    Contributor

    @tycho01 Thank you!

    Yes, you are right about f5. My remark is that it's wrong from a logical perspective. f7 is correct as well. {} says any type could work here (because of String(x: any) => string) and id just picks it up. It can be generalized to a type param but it won't change anything.

    PS: I've pushed a fix for what you are observing on f7. Check [1, 2, 3].map(id) for the new road block.

  10. KiaraGrouwstra commented on Dec 21, 2016

    @KiaraGrouwstra
    Contributor

    Hah, T[] for [1, 2, 3].map(id) huh. Don't think I'd seen that before, a generic without any binding...

  11. gcnew commented on Jan 11, 2017

    @gcnew
    Contributor

    I just pushed a greatly improved unification version. It works 80% of the time (with some corner cases left out). I'm still going through the failed test cases, but I think other people can give it a try as well.

  12. shutej commented on Apr 9, 2017

    @shutej

    Any ideas when/if this will land? Trying to determine if I should redesign my code (which assumed unbound types would be carried forward) or just wait for this to land...

  13. gcnew commented on Apr 9, 2017

    @gcnew
    Contributor

    I wouldn't depend on it. Currently it's not on the roadmap and the core TS team doesn't look very interested in this feature. To support it, the compiler will need some fairly substantial changes, or better said a rearchitecture of the inferencer. I've layed out the major hurdles in this comment.

  14. 3 remaining items

  15. removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    SuggestionAn idea for TypeScript
    on May 18, 2017
  16. KiaraGrouwstra commented on May 27, 2017

    @KiaraGrouwstra
    Contributor

    Fixed with #16072.

  17. gcnew commented on May 27, 2017

    @gcnew
    Contributor

    Fixed is a bit of an overstatement, but #16072 helps.

  18. KiaraGrouwstra commented on May 27, 2017

    @KiaraGrouwstra
    Contributor

    Marin Marinov (@gcnew) I've yet to really test; would you have examples still failing?

  19. gcnew commented on May 27, 2017

    @gcnew
    Contributor

    This PR is a precursor for inferring higher order function types when no inferences can be made for one or more type parameters.

    const wrapper = compose(x => [x], y => ({ p: y }));  // (x: {}) => { p: {}[] }

    We currently infer (x: {}) => { p: {}[] }, but ideally we'd infer (x: A) => { p: A[] }. That's next on the list.

    All polymorphic higher order uses still continue to fail as noted in the PR. For example, all flip tests that I used are still failing. What has changed is that if you provide a type annotation it will be used during inference, e.g.:

    declare function flip<A, B, R>(f: (a: A, b: B) => R): (b: B, a: A) => R;
    declare function fconst<X, Y>(x: X, y: Y): X;
    
    const f1 = flip(fconst); // (b: {}, a: {}) => {}
    
    const f2: (b: string, a: number) => number = flip(fconst); // OK, now it's working
    const f3: (b: string, a: number) => string = flip(fconst); // Error correctly caught
    
    // assignment works but all for the wrong reasons
    // flip's A, B and R are inferred as `any` :/
    const f4: <A, B>(a: A, b: B) => B = flip(fconst);
    
    // uncaught error and implicit any not reported :/
    const f5: <A, B>(a: A, b: B) => A = flip(fconst);

    #16072 makes f2 type check while previously it didn't. All the rest are pretty much the same. f4 and f5 are now inferred with any instead of {}, however this makes no actual difference because of #5616.

  20. gcnew commented on Jun 6, 2017

    @gcnew
    Contributor

    I made a quick implementation of my suggestion from #16114 (comment). This work (attempt4) builds on #16104 and shows promising results, however there are still some important issues left. I do believe that this approach is working and the issues will be resolved with more effort put into them.

    Issues:

    • when the invocation of a function yields a polymorphic function (e.g. flip(fconst)), the prototype hacks the resolved return type to contain the type parameters on itself. Thats incorrect and creates many problems.
    • mapped type inference doesn't always work
    • type paramaters sometimes leak out when they should be inferred as {}. This is very apparent when creating a class instance without providing the required type parameters. An alternative approach would be to implement skolems/rank 2 types but they don't seem to be the best fit.
    • contextually typed functions are not always fully checked
    • deferred constraints are not currently solved
    • top level literal types are not always handled as before

    The test cases that I've used are here (gist). With a few exceptions, most of them yield what's expected.

    PS: The code is not well written. My primary aim was to verify that the proposed logic could be made to work. If all goes well, I'll refactor it into something maintainable.

    PS2: This version of the compiler cannot build itself. However, it works fine if built externally. I've modified the jakefile so that jake local builds the compiler with the last known good one.

  21. mhegazy commented on Aug 17, 2017

    @mhegazy
    Contributor

    Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

  22. zpdDG4gta8XKpMCd commented on Sep 21, 2017

    @zpdDG4gta8XKpMCd
    Author

    Mohamed Hegazy (@mhegazy) problem still there, please reopen

  23. mhegazy commented on Sep 21, 2017

    @mhegazy
    Contributor

    It is a duplicate/same underlying issue as #9366.

  24. locked and limited conversation to collaborators on Jun 19, 2018
  25. ahejlsberg commented on Mar 8, 2019

    @ahejlsberg
    Member

    Fixed in #30215.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already createdFixedA PR has been merged for this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions