Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify the Observable pipe with the help of Typescript Variadic Functions #7481

Open
hansschenker opened this issue Jun 11, 2024 · 3 comments

Comments

@hansschenker
Copy link

Describe the bug

// Define a type alias for variadic functions
type PipeFunctions = [(source: Observable) => any, ...Array<UnaryFunction<any, any>>];

class Observable {
// Simplified pipe method using variadic tuple types
pipe(...operations: PipeFunctions): R {
return _pipe(...operations)(this as any);
}
}


Explanation:

Type Alias for Variadic Functions:

PipeFunctions is a tuple type

  • where the first element is a function that takes an Observable and
    returns any,
  • followed by any number of UnaryFunction<any, any>.

Generic Pipe Method:

The pipe method is defined with a single generic parameter R to infer the return type based on the provided operations.

The method accepts a rest parameter ...operations typed as PipeFunctions.

Using _pipe Function:

Within the pipe method, the operations are passed to the _pipe function using the spread operator ...operations.

The result of _pipe(...operations) is then called with this (the current Observable instance), which effectively chains the operations together.

This approach significantly reduces the number of overload signatures required and leverages TypeScript's variadic tuple types to handle any number of operations in a type-safe manner.

Expected behavior

// Define a type alias for variadic functions
type PipeFunctions = [(source: Observable) => any, ...Array<UnaryFunction<any, any>>];

class Observable {
// Simplified pipe method using variadic tuple types
pipe(...operations: PipeFunctions): R {
return _pipe(...operations)(this as any);
}
}


Explanation:

Type Alias for Variadic Functions:

PipeFunctions is a tuple type

  • where the first element is a function that takes an Observable and
    returns any,
  • followed by any number of UnaryFunction<any, any>.

Generic Pipe Method:

The pipe method is defined with a single generic parameter R to infer the return type based on the provided operations.

The method accepts a rest parameter ...operations typed as PipeFunctions.

Using _pipe Function:

Within the pipe method, the operations are passed to the _pipe function using the spread operator ...operations.

The result of _pipe(...operations) is then called with this (the current Observable instance), which effectively chains the operations together.

This approach significantly reduces the number of overload signatures required and leverages TypeScript's variadic tuple types to handle any number of operations in a type-safe manner.

Reproduction code

// Define a type alias for variadic functions
type PipeFunctions<T> = [(source: Observable<T>) => any, ...Array<UnaryFunction<any, any>>];

class Observable<T> {
  // Simplified pipe method using variadic tuple types
  pipe<R>(...operations: PipeFunctions<T>): R {
    return _pipe(...operations)(this as any);
  }
}

------------------------
Explanation:

Type Alias for Variadic Functions:

PipeFunctions<T> is a tuple type 
- where the first element is a function that takes an Observable<T> and  
  returns any, 
- followed by any number of UnaryFunction<any, any>.

Generic Pipe Method:

The pipe method is defined with a single generic parameter R to infer the return type based on the provided operations.

The method accepts a rest parameter ...operations typed as PipeFunctions<T>.

Using _pipe Function:

Within the pipe method, the operations are passed to the _pipe function using the spread operator ...operations.

The result of _pipe(...operations) is then called with this (the current Observable instance), which effectively chains the operations together.

This approach significantly reduces the number of overload signatures required and leverages TypeScript's variadic tuple types to handle any number of operations in a type-safe manner.

Reproduction URL

No response

Version

whatever

Environment

yes

Additional context

yes

@hansschenker
Copy link
Author

I like this idea!

@vitaly-t
Copy link

vitaly-t commented Aug 4, 2024

I like this idea!

Perhaps because it is your own idea? 🥲 Perhaps you like all your ideas? 🥲

P.S. I like my comment!

@voliva
Copy link
Contributor

voliva commented Aug 5, 2024

@hansschenker sorry I did a 👎 without giving a reason.

The pipe operator is not using TS' variadic arguments and is using many overloads instead because it needs to have some properties that, at least on the previous TS versions, it was impossible to achieve.

You need to have a pipe function that can automatically infer the value of each Operator within the chain, as well as find incompatibilities:

from([1,2,3]).pipe(
  map(v => {
    // TS knows v is a number, so you can run .toFixed() on it
    return v.toFixed();
  }),
  switchMap(v => {
    // TS knows v is a string
    ...
  })
);

There has been multiple attempts at leveraging TS variadic types to simplify that and also allow for a potentially infinite amount of arguments, but so far it has been just not possible to do. Probably the most advanced one was #6671, unfortunately he removed the gist, but it did allow an arbitrary amount of arguments and it would check that every type matches. The problem was that you had to manually fill in all of the type annotations and it would just perform the check, throwing a "is not assignable to never" in case it failed.

Your proposed solution:

// Define a type alias for variadic functions
type PipeFunctions<T> = [(source: Observable<T>) => any, ...Array<UnaryFunction<any, any>>];

is just using any everywhere, so no inference will be done and no type checking will happen whatsoever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants