Skip to content

Improve rest Generics in 3.0 RC sufficiently to support RxJS Observable.prototype.pipe #25660

Closed as not planned

Description

@benlesh

Search Terms

variadic pipe rxjs

Suggestion

Improve variadic kind support to allow determining the final part of the kind in some way. The problem is currently in 3.0-rc, I haven't found a way to improve RxJS's pipe method. There are a lot of people using TypeScript with RxJS and this has been a pain point for all of them.

Maybe some sort of syntax like:

declare type OperatorFunction<T, R> = (source: Observable<T>) => Observable<R>;

class Observable<T> {
  pipe<R, U extends [OperatorFunction<T, any>, ...OperatorFunction<any, any>[], OperatorFunction<any, R>]>(...operators: U): Observable<R>; 
}

It's a tricky case because type inferance also has to work.

Use Cases

RxJS currently has a lot of overloads just to support what is really a common functional programming feat.

Examples

Mostly I'd use this as shown above to define the pipe method on Observable.

Really I'm looking for any solution to this problem though. It's a very common complaint from our TypeScript users.

🙏

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)

Activity

  1. benlesh commented on Jul 14, 2018

    @benlesh
    Author

    Related: #5453

  2. DanielRosenwasser commented on Jul 14, 2018

    @DanielRosenwasser
    Member

    #24897 didn't claim to solve everything in #5453, and I don't think we're expecting any new changes in 3.0, but we'll be continuing to think about solutions for variadic types.

    That doesn't mean this problem won't get solved in another 3.x release though, so hopefully you don't think you're running out of time or anything like that.

  3. fbartho commented on Jul 14, 2018

    @fbartho

    Ben Lesh (@benlesh) I just added this to type-zoo:

    export type ParamTypes<F extends Function> = // tslint:disable-line
      F extends () => any ? {} :
      F extends (p0: infer P0) => any ? [P0] :
      F extends (p0: infer P0, p1: infer P1) => any ? [P0, P1] :
      F extends (p0: infer P0, p1: infer P1, p2: infer P2) => any ? [P0, P1, P2] :
      F extends (p0: infer P0, p1: infer P1, p2: infer P2, p3: infer P3) => any ? [P0, P1, P2, P3] :
      // ... -- extend this at your own risk, this could be bad for compilation performance!
      never;

    https://github.com/pelotom/type-zoo/blob/master/types/index.d.ts#L66-L77

    I don't know how to do this in a variadic way to work for the pipe function, but you could manually type it for up to N-piped helpers.

    For your use case, obviously, infer the whole type, and extract the ReturnType, and somehow chain them together.

    Thoughts?

  4. sirian commented on Jul 16, 2018

    @sirian
    Contributor

    Frederic Barthelemy (@fbartho)
    seems like this working:

    export type ArgTypes<F> = F extends ((...args: infer T) => any) ? T : never;
    

    tests:

    declare function argTypes<F>(fn: F): ArgTypes<F>
    
    // $ExpectType []
    argTypes(() => true);
    
    // $ExpectType [string]
    argTypes((x: string) => true);
    
    // $ExpectType [string, boolean]
    argTypes((x: string, y: boolean) => true);
    
    // $ExpectType [string, boolean, ...symbol[]]
    argTypes((x: string, y: boolean, ...args: symbol[]) => true);
    
    // $ExpectType [string, boolean, Date]
    argTypes((x: string, y: boolean, z: Date) => true);
    
  5. fbartho commented on Jul 16, 2018

    @fbartho

    sirian You're kidding! That's great. I think we just all assumed that that would be too good to be true, and never thought to try it. https://github.com/pelotom/type-zoo is the repo I was talking about if you want to join in ;)

  6. sirian commented on Jul 16, 2018

    @sirian
    Contributor

    Frederic Barthelemy (@fbartho) ArgCount also is pretty simple

    export type ArgCount<F> =
        FunctionConstructor extends F ? number :
        F extends (...args: infer T) => any ? T["length"] : never;

    BTW, at 2.9 I used trick with inversed check of function signatures (((...) => any) extends F instead of F extends (...) => any). Maybe this information would be helpful

    export type Fn0<R = any> = () => R;
    export type Fn1<R = any, P1 = any> = (p1: P1) => R;
    export type Fn2<R = any, P1 = any, P2 = any> = (p1: P1, p2: P2) => R;
    export type Fn3<R = any, P1 = any, P2 = any, P3 = any> = (p1: P1, p2: P2, p3: P3) => R;
    export type Fn4<R = any, P1 = any, P2 = any, P3 = any, P4 = any> = (p1: P1, p2: P2, p3: P3, p4: P4) => R;
    export type FnX<R = any, P1 = any, P2 = any, P3 = any, P4 = any, P5 = any> = (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, ...args: any[]) => R;
    
    export type ArgCount<F> =
        FunctionConstructor extends F ? number :
    
        F extends FnX<infer R, infer P1, infer P2, infer P3, infer P4, infer P5> ? (
            FnX<R, P1, P2, P3, P4, P5> extends F ? number :
            Fn4<R, P1, P2, P3, P4> extends F ? 4 :
            Fn3<R, P1, P2, P3> extends F ? 3 :
            Fn2<R, P1, P2> extends F ? 2 :
            Fn1<R, P1> extends F ? 1 : 0
            ) : never;
    
    export type ArgType<F, N extends number> = F extends FnX<any, infer P1, infer P2, infer P3, infer P4, infer P5> ? (
        N extends 0 ? never :
        N extends 1 ? (Fn1 extends F ? P1 : never) :
        N extends 2 ? (Fn2 extends F ? P2 : never) :
        N extends 3 ? (Fn3 extends F ? P3 : never) :
        N extends 4 ? (Fn4 extends F ? P4 : never) :
        any
        ) : never;
  7. ahejlsberg commented on Jul 16, 2018

    @ahejlsberg
    Member

    Ben Lesh (@benlesh) Much as I share your distaste for walls of overloads, this actually seems like a case where it is merited. The manner in which types flow from the output of one argument to the input of the next would be really hard to capture in a single higher-order construct (and, to be sure, there are just so many different ways the types might flow that elevating a single pattern to a new construct seems questionable).

    Beyond the issue of only handling a fixed maximum number of arguments, what are the issues caused by the overload pattern? I'm curious about the specific pain point you refer to.

  8. benlesh commented on Aug 27, 2018

    @benlesh
    Author

    Mostly it's a fixed maximum number of arguments... after that, the typings get manual and squishy and I get complaints.

    If we ever get a pipeline operator, this problem effectively goes away.

  9. benlesh commented on Aug 27, 2018

    @benlesh
    Author

    Daniel Rosenwasser (@DanielRosenwasser) Anders Hejlsberg (@ahejlsberg) ... I'm not sure whether to keep this open or close it. On one hand, it's an issue that people are going to hit. On the other hand, nothing is going to be done about it.

    🤷‍♂️

    Leave open, mark "won't fix"?

  10. added
    Too ComplexAn issue which adding support for may be too complex for the value it adds
    and removed
    Needs More InfoThe issue still hasn't been fully clarified
    on Aug 28, 2018
  11. steve-taylor commented on Aug 22, 2020

    @steve-taylor

    Does TypeScript 4’s variadic tuple types help solve this issue? I’m struggling to get my head around it, so I genuinely don’t know the answer.

  12. benlesh commented on Aug 26, 2020

    @benlesh
    Author

    Steve Taylor (@steve-taylor) this particular issue with pipe, no. It solves loads of other issues, but this core issue is still a problem.

  13. steve-taylor commented on Aug 26, 2020

    @steve-taylor

    See proposal in #30370.

  14. typescript-bot commented on Jun 21, 2023

    @typescript-bot
    Contributor

    This issue has been marked as "Too Complex" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  15. benlesh commented on Jun 21, 2023

    @benlesh
    Author

    I'm not sure you want to close this as someone else is likely to report it. May be add the topic somewhere else with an explainer about why it can't be done? Then close? Not my repo. Not my call.

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

    SuggestionAn idea for TypeScriptToo ComplexAn issue which adding support for may be too complex for the value it adds

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions