The art of combining TypeScript recursive functions

Looking to establish a function chain input for a pipe/flow/compose function.

Is it feasible without directly expanding the types to a certain depth, contrary to common practice? Check out lodash's flow.

The goal is to enforce data flow type checking within the chain. - Each function takes the result of the previous one as an argument - The first argument is a template parameter - The final return value is also a template parameter

type Chain<In, Out, Tmp1 = any, Tmp2 = any> = [] | [(arg: In) => Out] | [(arg: In) => Tmp1, (i: Tmp1) => Tmp2, ...Chain<Tmp2, Out>];

The concept is still in the draft stage.

However, there are a few errors that crop up:

  1. Type alias 'Chain' circularly references itself.
    (need to understand why and find a resolution)
  2. A rest element type must be an array type.
    (potentially due to limitations with generic tuples)
  3. Type 'Chain' is not generic. (perplexing error without clear explanation)

Is this particular definition for Chain workable in TypeScript? If so, please provide a code snippet.

(Tested on the latest tsc version 3.1.6)

Answer №1

Circular type aliases are not widely supported, except in specific scenarios. (UPDATE: As of TS 4.1, there is better support for circular type aliases. However, representing flow() as operating on AsChain that verifies a specific array of functions seems more viable than trying to match all valid arrays of functions with a single Chain)

Instead of directly translating the particular type you've defined into TypeScript syntax, I'll consider your question as follows: how can we type a function like flow(), which accepts multiple one-argument functions and connects them into a chain where each function's return type matches the next function's argument type... ultimately returning a collapsed chain as a single one-argument function?

I have come up with a solution that seems to work, although it involves some complexity with the use of conditional types, tuple spreads, and mapped tuples. Here is the implementation:

// Type Definitions
type Lookup<T, K extends keyof any, Else=never> = K extends keyof T ? T[K] : Else
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never;
type Func1 = (arg: any) => any;
type ArgType<F, Else=never> = F extends (arg: infer A) => any ? A : Else;
type AsChain<F extends [Func1, ...Func1[]], G extends Func1[]= Tail<F>> =
  { [K in keyof F]: (arg: ArgType<F[K]>) => ArgType<Lookup<G, K, any>, any> };
type Last<T extends any[]> = T extends [...infer F, infer L] ? L : never;
type LaxReturnType<F> = F extends (...args: any) => infer R ? R : never;

// Function Declaration
declare function flow<F extends [(arg: any) => any, ...Array<(arg: any) => any>]>(
  ...f: F & AsChain<F>
): (arg: ArgType<F[0]>) => LaxReturnType<Last<F>>;

Testing the Implementation:

// Test Cases
const stringToString = flow(
  (x: string) => x.length, 
  (y: number) => y + "!"
); // okay

const str = stringToString("hey"); // it produces a string

const tooFewParams = flow(); // error

const badChain = flow(
  (x: number)=>"string", 
  (y: string)=>false, 
  (z: number)=>"oops"
); // error, boolean not assignable to number

The explanation of type definitions is intricate, but here's a brief guide on using them:

  • Lookup<T, K, Else> tries to retrieve T[K], falling back to Else if it doesn't exist. For instance,

    Lookup<{a: string}, "a", number>
    results in string, and
    Lookup<{a: string}, "b", number>
    yields number.

  • Tail<T> removes the first element from a tuple type T. Thus,

    Tail<["a","b","c"]>
    becomes ["b","c"].

  • Func1 represents a one-argument function.

  • ArgType<F, Else> retrieves the argument type of F if it's a one-argument function; otherwise, it returns Else. For example,

    ArgType<(x: string)=>number, boolean>
    is string, while ArgType<123, boolean> is boolean.

  • AsChain<F> transforms a tuple of one-argument functions into a chain by adjusting each function's return type to match the subsequent function's argument type (using any for the last function). If AsChain<F> aligns with F, everything works correctly. Otherwise, F is an invalid chain. So,

    AsChain<[(x: string)=>number, (y:number)=>boolean]>
    would be
    [(x: string)=>number, (y: number)=>any]
    , which is acceptable. But
    AsChain<[(x: string)=>number, (y: string)=>boolean]>
    is
    [(x: string)=>string, (y: string)=>any]
    , deemed incorrect.

  • Last<T> picks out the last element from a tuple, essential for representing the return type of flow(). For instance,

    Last<["a","b","c"]>
    gives "c".

  • LaxReturnType<F> resembles ReturnType<F> without constraints on F.


That sums up the overview. Best of luck with your coding endeavors!

Link to Playground code

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

Retrieve the child elements belonging to a specific type of element

My challenge involves a table identified by the id of tableDiv. I am looking to dynamically reset the values of each table header and data cells, but due to the nature of the table being created with ASP.NET (GridView), I am unable to assign classes for th ...

Java applet failing to display in web browsers

<html> <head> <title>Applet</title> </head> <body> <applet code="Main.class" archive="Applet1.jar" WIDTH=400 HEIGHT=400></applet> <object codetype="application/java" classid="java:Main.class" a ...

Getting a comprehensive list of functions that can be used with a Class in Node.js

I'm on a quest to find a straightforward method to retrieve the list of functions available for a class in Node.js using the command line. After some online research, I came across Object.getOwnPropertyNames(), but it seems inconsistent as it works f ...

What is the best way to implement a function on every item within a specific class?

I'm new to coding and I have a website that showcases job listings. I want to create a function that dynamically changes the logo of a company based on the first 50 characters of the company name mentioned. The current function only works for the firs ...

Having trouble with setting up a Store with Ngrx in an Angular application

Could someone assist me in troubleshooting the error by analyzing the code provided below? I am new to Angular and unfamiliar with the Ngrx concept. You can also view a screenshot of the error here: https://i.sstatic.net/yhM0G.png package.json { "name": ...

Module `coc-tsserver` not found (error ts2307)

https://i.sstatic.net/k1MVW.png Working on a project using NeoVim with CoC for TypeScript development in a yarn-3 pnp-enabled environment. Suddenly, the editor stopped recognizing imports and started showing errors for non-existent modules (refer to the s ...

Exploring the capabilities of extending angular components through multiple inheritance

Two base classes are defined as follows: export class EmployeeSearch(){ constructor( public employeeService: EmployeeService, public mobileFormatPipe: MobileFormatPipe ) searchEmployeeById(); searchEmployeeByName(); } ...

Guide on transforming the best.pt model of YOLOv8s into JavaScript

After successfully training a custom dataset on YOLOv8s model using Google Colab, I now have the best.pt file that I want to integrate into a web app via JavaScript. I've come across mentions of TensorFlow.js as a potential solution, but I'm stil ...

Ensure that input field is validated only upon clicking the save button in Angular framework

Can anyone suggest the most efficient method to validate required fields in an input form using AngularJS? I want to display an error message only after the user clicks the save button (submit). Thank you in advance. ...

Showing error messages to users with Angular and Firebase

As a beginner in Angular JS, html, and css, I am facing a challenge with routing in my login component. When the user clicks submit, the application should redirect to the landing page upon successful authentication or return to the login page with an erro ...

Tailored production method based on specific criteria

One of my challenges is to create a versatile factory method using typescript. The main objective is to initialize a class based on its name using generics, instead of employing if/else or switch statements. I am aiming for similar functionality as this ...

The jQuery function text().replace('','') is failing to work properly

After spending countless hours attempting to remove a text string that I previously appended, I am still struggling. The script I have is responsible for managing an accordion and unfortunately contains some redundant text. My goal is to add and remove th ...

Invoke callback function to manage modal visibility

My project includes a modal for displaying a login/register form. The default behavior is that the modal opens when triggered by another component using the props show. This setup works perfectly when the modal is called by this specific component. Furthe ...

What is the process of extracting multiple attributes from an object that has been selected by a user using mat-options (dropdown list) in Angular?

Summary: A dropdown list contains objects, unsure how to capture multiple attributes of selected object. Current Implementation: I have successfully created a dropdown list that displays the details of an object retrieved through an API call: <mat-f ...

Cypress - AG-Grid table: Typing command causing focus loss in Cell

Encountering a peculiar issue: I am attempting to input a value into the cell using 'type()'. My suspicion is that each letter typed causes the focus on the cell to be lost. It seems that due to this constant loss of focus and the 'type()& ...

Ways to transmit information to ajax using both an image and a string

I'm facing an issue while trying to upload an image along with other data to my database. The code seems to be working fine for the image, but I'm unable to include the name and additional information along with it. var formdata = new FormData ...

What is the best way to secure videos and other static files with authentication in a next.js web application?

My goal is to provide static content, specifically videos, exclusively to authorized visitors. I want to secure routes so that they are only accessible to authenticated users. However, the challenge arises when trying to display a video on a page located i ...

What is the best way to locate the position of a different element within ReactJS?

Within my parent element, I have two child elements. The second child has the capability to be dragged into the first child. Upon successful drag and drop into the first child, a callback function will be triggered. What is the best way for me to determi ...

Experiencing an issue with excessive re-renders in React as it restricts the number of renders to avoid getting stuck in an infinite loop while attempting to

I am working with a React component import React, {useState} from 'react'; function App() { const [number, setNumber] = useState(12); return ( <> <h1>The number value is: {number}</h1> <div className=" ...

Need help setting up a time-based query in Laravel? Here's how to schedule it!

I am currently using the following query to update a status value. public function updateStatus(Request $request) { $customer = Customer::findOrFail($request->user_id); $customer->status = $request->status; $customer->new_customer_s ...