"Utilizing variadic tuple types to implement the pipe function in TypeScript 4: A step-by-step guide

An illustration from the release notes of TypeScript 4 demonstrates the use of variadic tuple types to eliminate multiple overload definitions. It seems feasible to type the pipe function for any number of arguments.

type F<P, R> = (p: P) => R

type Pipe2<T1, T2, R> = [F<T1, T2>, F<T2, R>]
type Pipe3<T1, T2, T3, R> = [F<T1, T2>, ...Pipe2<T2, T3, R>]
type Pipe4<T1, T2, T3, T4, R> = [F<T1, T2>, ...Pipe3<T2, T3, T4, R>]

function pipe<T1, R>(f1: F<T1, R>): F<T1, R>
function pipe<T1, T2, R>(...fns: Pipe2<T1, T2, R>): F<T1, R>
function pipe<T1, T2, T3, R>(...fns: Pipe3<T1, T2, T3, R>): F<T1, R>
function pipe<T1, T2, T3, T4, R>(...fns: Pipe4<T1, T2, T3, T4, R>): F<T1, R>
function pipe(...fns) {
  return x => fns.reduce((res, f) => f(res), x)
}

A starting point could be

function pipe<Fns>(...fns: PipeArgs<Fns>): PipeReturn<Fns>
function pipe(...fns) {
  return x => fns.reduce((res, f) => f(res), x)
}

We still need to define the helper types PipeArgs<Fns> and PipeReturn<Fns>. Is there another approach to achieve this?


Edit: With the current TypeScript version (4.1.2), achieving this seems more challenging. The rest parameter types in pipe need to be inferred with a specific structure. Here is an approach that includes a working PipeReturn<Fns> type.

(... code snippet ...)

Before presenting the various pipe signatures that didn't work as expected, let's explore some tests and examples to understand their behavior.

(... code snippet ...)

The following pipe signatures and the corresponding tests/examples that failed to meet expectations are highlighted.

(... code snippet ...)

By adding Fns & to the previous approach, the previous error was fixed but a new expected error did not occur.

(... code snippet ...)

Another idea is to enforce in the return type that Fns has the expected structure, but this definition also has an error.

(... code snippet ...)

Edit 2: Additionally, the ts-toolbelt library offers several type definitions to type your pipe function for up to 10 arguments, although not for an unlimited number of arguments.

Answer №1

It appears that the comment from Anders on this page may no longer be relevant.

type Foo = typeof foo
type Bar = typeof bar
type Baz = typeof baz

type Fn = (a: any) => any

type Head<T extends any[]> = T extends [infer H, ...infer _] ? H : never

type Last<T extends any[]> = T extends [infer _]
  ? never
  : T extends [...infer _, infer Tl]
  ? Tl
  : never
...

Explore in Playground

For improved error handling, consider using the fnts library which utilizes the Compose type.

Answer №2

Today, I encountered a problem that was quite similar to what I had to solve recently. I managed to find a solution that feels fairly straightforward.

// This code snippet retrieves the last type in a tuple of types
type Last<T extends readonly any[]> = T extends readonly [...any[], infer F]
    ? F
    : never;

// Loose*<T> returns never if T is not valid, instead of restricting T
type LooseParameters<T> = T extends (...args: infer Args) => any ? Args : never;
type LooseReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type LooseSetReturnType<NewType, T> = T extends (...args: infer Args) => any
    ? (...args: Args) => NewType
    : never;

/**
 * Returns T if T is a valid pipeline.
 *
 * Tries to determine what T should be if it's not. For example:
 *
 * Pipeline<[(f: any) => number, (f: number[]) => any]> =
 *    [(f: any) => number[], (f: number[]) => any]
 *
 * Note that only the return type of the first function has changed.
 */
type LoosePipeline<T extends readonly any[]> = T extends readonly [
    infer A,
    infer B,
    ...infer Rest
]
    ? readonly [
          LooseSetReturnType<LooseParameters<B>[0], A>,
          ...LoosePipeline<readonly [B, ...Rest]>
      ]
    : readonly [...T];

function pipe<T extends readonly ((arg: any, ...args: undefined[]) => any)[]>(
    ...pipeline: LoosePipeline<T>
) {
    return (arg: Parameters<T[0]>[0]): LooseReturnType<Last<T>> =>
        pipeline.reduce<any>((acc, elem) => elem(acc), arg);
}

const foo = (arg: string) => [arg.length];
const baz = (arg: number[]) => Math.max(...arg);
const bar = (arg: number) => [arg.toString()];

const check: string[] = pipe(foo, baz, bar)("hello");
const check2: string[] = pipe(baz, bar)([2]);

// @ts-expect-error
const check3 = pipe(baz, bar)("hello");

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring Typescript: Enhancing the functionality of `export = Joi.Root`

I've noticed that the types for @hapi/joi appear to be outdated - some configuration parameters mentioned in the official documentation are missing from the types. To address this, I am attempting to enhance the existing types. node_modules/@types/ha ...

Retrieve the additional navigation information using Angular's `getCurrentNavigation()

I need to pass data along with the route from one component to another and retrieve it in the other component's constructor: Passing data: this.router.navigate(['/coaches/list'], { state: { updateMessage: this.processMessage }, ...

Tips for configuring Visual Studio Code to utilize path mappings for handling automatic imports

In order to streamline my project and avoid messy paths, I am implementing absolute paths that will allow for consistent imports regardless of the file's location in the project tree. For this purpose, I made adjustments to the tsconfig.json: "paths ...

Webpack is encountering difficulties in locating the entry module when working with typescript

I've been working on integrating webpack into my typescript application. To get a better understanding of webpack, I decided to do a minimal migration. I started by cloning the Angular2 quickstart seed and added a webpack.config.js: 'use strict& ...

Looping Angular Components are executed

I am currently developing an Angular application and encountering an issue with my navbar getting looped. The problem arises when I navigate to the /home route, causing the navbar.component.html components to duplicate and appear stacked on top of each oth ...

Can a substring within a string be customized by changing its color or converting it into a different HTML tag when it is defined as a string property?

Let's discuss a scenario where we have a React component that takes a string as a prop: interface MyProps { myInput: string; } export function MyComponent({ myInput }: MyProps) { ... return ( <div> {myInput} </div> ...

"Techniques for extracting both the previous and current selections from a dropdown menu in Angular 2

How can I retrieve the previous value of a dropdown before selection using the OnChange event? <select class="form-control selectpicker selector" name="selectedQuestion1" [ngModel]="selectedQuestion1" (Onchange)="filterSecurityQuestions($event.t ...

Generating a composer method in TypeScript (Flow $Composer)

While flow supports $Compose functions, the equivalent mechanism seems to be missing in TypeScript. The closest thing I could find in TypeScript is something like https://github.com/reactjs/redux/blob/master/index.d.ts#L416-L460. Is there a native equivale ...

Ensure that the Observable is properly declared for the item list

.html // based on the error message, the issue seems to be in the HTML code <ion-card *ngFor="let invitedEvent of invitedEvents"> <ion-card-content> <img [src]="eventPhotoUrl$[invitedEvent.id] | async"> </ion ...

What are the steps to incorporate SignalR into a TypeScript project?

Working on an asp.net mvc 4.5 project with typescript in the client side, I successfully installed and configured signalR on the server side. To integrate it into my project, I also installed signalr.TypeScript.DefinitelyTyped with jquery. In my typescrip ...

Creating a generic union type component in Typescript/Angular 10

My interfaces are as follows: export interface Channel { id: number; name: string; } export interface TvChannel extends Channel { subscribed: boolean; } export interface RadioChannel extends Channel { // marker interface to distinguish radio chan ...

I am able to view the node-express server response, but unfortunately I am unable to effectively utilize it within my Angular2 promise

https://i.stack.imgur.com/d3Kqu.jpghttps://i.stack.imgur.com/XMtPr.jpgAfter receiving the object from the server response, I can view it in the network tab of Google Chrome Dev Tools. module.exports = (req, res) => { var obj = { name: "Thabo", ...

The outcome of using Jest with seedrandom becomes uncertain if the source code undergoes changes, leading to test failures

Here is a small reproducible test case that I've put together: https://github.com/opyate/jest-seedrandom-testcase After experimenting with seedrandom, I noticed that it provides consistent randomness, which was validated by the test (running it multi ...

The functionality of NgbModal in ng-bootstrap is experiencing issues and becoming unresponsive in ng-bootstrap version 15 and Angular version 16

Currently, I am in the process of upgrading my Angular app from version 15 to version 16. Following the documentation, I have updated the @ng-bootstrap/ng-bootstrap package to version 15. However, after this update, I am facing issues with the NgbModals no ...

Issue encountered while trying to determine the Angular version due to errors in the development packages

My ng command is displaying the following version details: Angular CLI: 10.2.0 Node: 12.16.3 OS: win32 x64 Angular: <error> ... animations, cdk, common, compiler, compiler-cli, core, forms ... language-service, material, platform-browser ... platfor ...

What is the most efficient way to retrieve a single type from a union that consists of either a single type or an array of types

Is there a way to determine the type of an exported union type by extracting it from an array, as illustrated in the example above? How can this be achieved without directly referencing the non-exported Type itself? interface CurrentType { a: string; b ...

How to empty an array once all its elements have been displayed

My query pertains specifically to Angular/Typescript. I have an array containing elements that I am displaying on an HTML page, but the code is not finalized yet. Here is an excerpt: Typescript import { Component, Input, NgZone, OnInit } from '@angul ...

Merge attributes from objects within an array

I am seeking assistance with a basic task in TypeScript as a newcomer to the language. My challenge involves manipulating an array of objects like this: // Sample data let boop = [ {a: 5, b: 10}, {a: 7, c: 8}, {a: 6, b: 7, c: 9} ]; My objectiv ...

Import statement cannot be used except within a module

I am currently facing an issue with running the production version of my code. I have Node 20.10 and TypeScript 5 installed, but for some reason, I am unable to run the built version. Here are the contents of my package.json and tsconfig.json files: { & ...

Error: The AppModule encountered a NullInjectorError with resolve in a R3InjectorError

I encountered a strange error in my Angular project that seems to be related to the App Module. The error message does not provide a specific location in the code where it occurred. The exact error is as follows: ERROR Error: Uncaught (in promise): NullInj ...