A generically typed pipe function in TypeScript

This page summarizes the projects mentioned and recommended in the original post on news.ycombinator.com

Our great sponsors
  • SurveyJS - Open-Source JSON Form Builder to Create Dynamic Forms Right in Your App
  • WorkOS - The modern identity platform for B2B SaaS
  • InfluxDB - Power Real-Time Data Analytics at Scale
  • froebel

    A strictly typed utility library.

  • This is quite elegant!

    Direct link to the source of OP's pipe function and recursive type definitions: https://github.com/MathisBullinger/froebel/blob/main/pipe.ts...

    On a related note, I've been frustrated by the slow progress on https://github.com/tc39/proposal-pipeline-operator - specifically the thread in https://github.com/tc39/proposal-pipeline-operator/issues/91 which has 668 comments over 4+ years and shows no meaningful sign of consensus.

    The TC39 group is (justifiably) very concerned about backwards and future compatibility, and Typescript has a policy of not introducing syntax that is in scope for Javascript itself until the syntax has formally reached a stable state (see: https://github.com/Microsoft/TypeScript/issues/2103#issuecom...) - so we're far from having a pure operator for this.

    But `pipe((n: number) => n.toString(), (a: string) => a+' ')(3)` is as clean as I've ever seen it get.

    And to use it in an ad-hoc way for left-to-right readability (and for code that will be maintained by those who think curry is just a tasty dish), it's trivial to implement an applyPipe on top:

        applyPipe('foo', strip, title, (s: string) => `${s}: bar`)

  • fp-ts

    Functional programming in TypeScript

  • This is great, thanks for sharing.

    I'm currently using @arrows/composition https://caderek.github.io/arrows/packages/composition/#pipe because of the light way library approach, which you seem to share.

    I also try fp-ts https://github.com/gcanti/fp-ts but is a bit an academic style, so difficult to introduce in everyday work. Implementation of pipe: https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe

    I still need to wrap my head on your use of generics, but yours looks more flexible than the static type approach that other libraries (include RxJS) implements, does your pipe support types for any length of arguments? Does require a specific version of TypeScript?

    Nice work.

  • SurveyJS

    Open-Source JSON Form Builder to Create Dynamic Forms Right in Your App. With SurveyJS form UI libraries, you can build and style forms in a fully-integrated drag & drop form builder, render them in your JS app, and store form submission data in any backend, inc. PHP, ASP.NET Core, and Node.js.

    SurveyJS logo
  • RxJS

    A reactive programming library for JavaScript

  • For a while now I've struggled to implement the type definition for that function, so that every passed-in function can only accept the return type of the previous function as its parameter and the resulting function will take the arguments of the first function as its parameters and return the type of the last function.

    The main problem with this is that trying to define a type for pipe's arguments up-front would require typing them as an infinitely-recursive type that TypeScript cannot handle. A common workaround for this is to define pipe's type separately for every number of arguments it can take. This is for example how RXJS defines its pipe function: https://github.com/ReactiveX/rxjs/blob/f174d38554d404f21f98a...

    Other common solutions are to make concessions like requiring all functions to have the same return type or letting pipe only take one function at a time and returning an object with methods to add another function and invoke the chain. None of these solutions are satisfying in my opinion.

    I think I've finally found an implementation that fulfills all these criteria. The argument and return types of passed in functions are correctly enforced (no matter the number of passed-in functions) and the pipe function returns a function that accepts the arguments of the first function, invokes all functions in turn with the result of the last, and correctly returns the type of the last function of the chain. Including asynchronous functions in the chain works to: if a function returns a promise that promise is resolved before being passed into the next function and the function returned from pipe will return a promise as its type.

    There is one disadvantage to the implementation that I'm aware of: When passing in anonymous functions, the types of their arguments can not be inferred if they aren't annotated. That means that

      pipe(() => 10, n => n.toString())

  • tslib

    utils library in Typescript (by beenotung)

  • An easier alternative is to wrap the value into an array, then use .map for each function in the chain, and finally escape the value with [0]

    I made a similar data structure to allow adding side effect (no return value) as part of the chained function.

    https://github.com/beenotung/tslib/blob/9f9a9274c1e13be7ba83...

  • proposal-pipeline-operator

    A proposal for adding a useful pipe operator to JavaScript.

  • This is quite elegant!

    Direct link to the source of OP's pipe function and recursive type definitions: https://github.com/MathisBullinger/froebel/blob/main/pipe.ts...

    On a related note, I've been frustrated by the slow progress on https://github.com/tc39/proposal-pipeline-operator - specifically the thread in https://github.com/tc39/proposal-pipeline-operator/issues/91 which has 668 comments over 4+ years and shows no meaningful sign of consensus.

    The TC39 group is (justifiably) very concerned about backwards and future compatibility, and Typescript has a policy of not introducing syntax that is in scope for Javascript itself until the syntax has formally reached a stable state (see: https://github.com/Microsoft/TypeScript/issues/2103#issuecom...) - so we're far from having a pure operator for this.

    But `pipe((n: number) => n.toString(), (a: string) => a+' ')(3)` is as clean as I've ever seen it get.

    And to use it in an ad-hoc way for left-to-right readability (and for code that will be maintained by those who think curry is just a tasty dish), it's trivial to implement an applyPipe on top:

        applyPipe('foo', strip, title, (s: string) => `${s}: bar`)

  • TypeScript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • This is quite elegant!

    Direct link to the source of OP's pipe function and recursive type definitions: https://github.com/MathisBullinger/froebel/blob/main/pipe.ts...

    On a related note, I've been frustrated by the slow progress on https://github.com/tc39/proposal-pipeline-operator - specifically the thread in https://github.com/tc39/proposal-pipeline-operator/issues/91 which has 668 comments over 4+ years and shows no meaningful sign of consensus.

    The TC39 group is (justifiably) very concerned about backwards and future compatibility, and Typescript has a policy of not introducing syntax that is in scope for Javascript itself until the syntax has formally reached a stable state (see: https://github.com/Microsoft/TypeScript/issues/2103#issuecom...) - so we're far from having a pure operator for this.

    But `pipe((n: number) => n.toString(), (a: string) => a+' ')(3)` is as clean as I've ever seen it get.

    And to use it in an ad-hoc way for left-to-right readability (and for code that will be maintained by those who think curry is just a tasty dish), it's trivial to implement an applyPipe on top:

        applyPipe('foo', strip, title, (s: string) => `${s}: bar`)

  • async-utils

    Async function utils

  • I had built an "async" version of this - https://github.com/rocky-jaiswal/async-utils/blob/main/src/p...

    Basically can solve a lot of problems by passing state to a list of "piped" functions which modify the state and eventually generate an output.

  • WorkOS

    The modern identity platform for B2B SaaS. The APIs are flexible and easy-to-use, supporting authentication, user identity, and complex enterprise features like SSO and SCIM provisioning.

    WorkOS logo
  • ramda

    :ram: Practical functional Javascript

  • )

    This is how the ramda library does it[0]

    [0] https://github.com/ramda/ramda

  • ramdaP-ts

    TypeScript ramda utility functions for dealing with Promises (and a few misc others)

  • Um, Ramda has pipe and types: https://www.npmjs.com/package/@types/ramda. Although I liked my own implementation of a pipeP (pipe that automatically unwraps promises) that let you specify the type of the input and output of the pipeline: https://github.com/chughes87/ramdaP-ts/blob/main/index.ts#L1....

  • DefinitelyTyped

    The repository for high quality TypeScript type definitions.

  • Looks like they define pipe separately for every number of arguments: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...

  • remeda

    A utility library for JavaScript and TypeScript.

  • IxJS

    The Interactive Extensions for JavaScript

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts