question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Pipe/flow/chain type support

See original GitHub issue

Search Terms

recursive, flow, pipe, chain

Suggestion

I do not have a specific implementation in mind to support this. There is a decent existing solution by a stackoverflow user @jcalz, which does its best given what typescript is able to accomplish now. I have created this issue to more to specifically point out the current limitation of typescript in regard to this feature, and the current use cases. This feature request falls under the larger umbrella of recursive type inference.

At its most broad, I would like to suggest that typescript include some form of an official Chain type that developers can use.

Unanswered Questions

  • should this type have an official implementation with a max recursion value? pipe: Chain<In, Out, number>
  • will this design pattern cease to be relevant if the tc39 pipeline proposal is integrated into javascript?

Use Cases

There are a number of implementations for javascript pipes in common libraries.

Related Issues

https://github.com/Microsoft/TypeScript/issues/27102 https://github.com/Microsoft/TypeScript/issues/28505

Examples

a typical implementation looks like this:

function pipe<T, A>(arg1: (in: T) => A): A
function pipe<T, A, B>(arg1: (in: T) => A, arg2: (in: A) => B): (in: T) => B
function pipe(...args: any[]): (in: any) => any


const strToNumber = (str: string) => parseInt(str)
const add = (x: number) => (y: number) => x + y
const safeAdder = pipe(strToNumber, add(5))
const out: number = safeAdder('6') // returns 11

The limitation of the current implementation is that if you use a typed pipe function with more arguments than the written overloads, you run lose type inference.

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. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:15
  • Comments:11

github_iconTop GitHub Comments

8reactions
profound7commented, Mar 26, 2022

I managed to implement a pipe function type. Instead of overloading, it uses tuple rest args and recursion.

There are 2 flavors of the pipe type. One returns a function (Pipe), and the other has a value as the first argument and immediately executes (PipeValue).

// A is argument type, R is return type
type Fn<A = any, R = any> = (a: A) => R;

type PipeResult<A, T extends Fn[]> = T extends [Fn<any, infer R>, ...infer Rest]
    ? Rest extends [Fn, ...any]
        ? PipeResult<A, Rest>
        : Fn<A, R>
    : never;

type PipeArgs<A, T extends Fn[]> = T extends [Fn<any, infer R>, ...infer Rest]
    ? Rest extends [Fn, ...any]
        ? [Fn<A, R>, ...PipeArgs<R, Rest>]
        : [Fn<A, R>]
    : T;

// usage: pipe(fn0, fn1, fn2...)(x)
type Pipe = <A, T extends Fn[]>(...fns: PipeArgs<A, T>) => PipeResult<A, T>;

// usage: pipe(x, fn0, fn1, fn2...)
type PipeValue = <A, T extends Fn[]>(a: A, ...fns: PipeArgs<A, T>) => ReturnType<PipeResult<A, T>>;

// example implementation (I apologize for my liberal use of any)
const pipe: Pipe = ((...fns: any[]) => (x: any) => fns.reduce((v, f) => f(v), x)) as any;
const pipeValue: PipeValue = ((x: any, ...fns: any[]) => fns.reduce((v, f) => f(v), x)) as any;

It correctly identifies the first incorrect argument.
The return type of the pipe function is correctly inferred even if there are incorrect arguments.

pipe

For compose, just switch around the inferring of A and R

type ComposeResult<R, T extends Fn[]> = T extends [Fn<infer A, any>, ...infer Rest]
    ? Rest extends [Fn, ...any]
        ? ComposeResult<R, Rest>
        : Fn<A, R>
    : never;

type ComposeArgs<R, T extends Fn[]> = T extends [Fn<infer A, any>, ...infer Rest]
    ? Rest extends [Fn, ...any]
        ? [Fn<A, R>, ...ComposeArgs<A, Rest>]
        : [Fn<A, R>]
    : T;

type Compose = <R, T extends Fn[]>(...fns: ComposeArgs<R, T>) => ComposeResult<R, T>;

const compose: Compose = ((...fns: any[]) => (x: any) => fns.reduceRight((v, f) => f(v), x)) as any;
1reaction
Azarattumcommented, Aug 28, 2022

I’ve made an updated version which:

  • Supports a pipeline pattern (with automatic pipe inference)
  • Supports inference when used as a callback
  • Supports spread arguments (with the best inference possible)

Playground

Code

type Fn<A = any[], Z = any> = (...args: A extends any[] ? A : [A]) => Z;

type Arg1<A, Z> = [Fn<A, Z>];
type Arg2<A, B, Z> = [Fn<A, B>, Fn<[B], Z>];
type Arg3<A, B, C, Z> = [...Arg2<A, B, C>, Fn<[C], Z>];
type Arg4<A, B, C, D, Z> = [...Arg3<A, B, C, D>, Fn<[D], Z>];
type Arg5<A, B, C, D, E, Z> = [...Arg4<A, B, C, D, E>, Fn<[E], Z>];
type Arg6<A, B, C, D, E, F, Z> = [...Arg5<A, B, C, D, E, F>, Fn<[F], Z>];
type Arg7<A, B, C, D, E, F, G, Z> = [...Arg6<A, B, C, D, E, F, G>, Fn<[G], Z>];
type ArgAny<A, B, C, D, E, F, G, H, Z extends Fn[]> = [
  ...Arg7<A, B, C, D, E, F, G, H>,
  ...Args<H, Z>
];

type Args<A, Z extends Fn[]> = Z extends [Fn<any, infer R>, ...infer Rest]
  ? Rest extends [Fn, ...any]
    ? [Fn<A, R>, ...Args<R, Rest>]
    : [Fn<A, R>]
  : Z;

type Result<Z> = Z extends [...any, Fn<any[], infer R>]
  ? R
  : Z extends Fn<any[], infer R>[]
  ? R
  : Z;

type To<A, Z, Y> = Y extends false
  ? Fn<A extends [infer T] ? T : A, Result<Z>>
  : Result<Z>;

type Pipe<X = unknown, Y = false> = {
  <A = X>(): To<A, A extends any[] ? A[0] : A, Y>;
  <Z, A = X>(..._: Arg1<A, Z>): To<A, Z, Y>;
  <B, Z, A = X>(..._: Arg2<A, B, Z>): To<A, Z, Y>;
  <B, C, Z, A = X>(..._: Arg3<A, B, C, Z>): To<A, Z, Y>;
  <B, C, D, Z, A = X>(..._: Arg4<A, B, C, D, Z>): To<A, Z, Y>;
  <B, C, D, E, Z, A = X>(..._: Arg5<A, B, C, D, E, Z>): To<A, Z, Y>;
  <B, C, D, E, F, Z, A = X>(..._: Arg6<A, B, C, D, E, F, Z>): To<A, Z, Y>;
  <B, C, D, E, F, G, Z, A = X>(..._: Arg7<A, B, C, D, E, F, G, Z>): To<A, Z, Y>;
  <B, C, D, E, F, G, H, Z extends Fn[], A = X>(
    ..._: ArgAny<A, B, C, D, E, F, G, H, Z>
  ): To<A, Z, Y>;
  <Z extends Fn[], A = X>(..._: Args<A, Z>): To<A, Z, Y>;
};

type Pipeline = <T extends any[]>(...data: T) => Pipe<T, true>;

Examples

// Pipe simple example
const data = pipe(
  (x: boolean, s: string) => 123,
  (x) => [],
  (x) => ({}),
  (x) => 123,
  (x) => "123",
  (x) => false,
  (x) => "123",
  // Arguments below are not inferred automatically
  (x: string) => 123,
  (x: number) => 123,
  (x) => 123,
  (x: number) => ["123"],
);
const result1 = data(true, "123");

// Pipeline verbose example
const process = pipeline(123, 321);
const result2 = process(
  (x, y) => x * y,
// ^? (parameter) x: number
  (x) => x.toString(), 
  (x) => x.toUpperCase()
)

// Pipeline simple example
const result3 = pipeline("123")(
  (x) => x.toUpperCase(),
  (x) => ({ data: x })
)

// Function callback example
function test(callback: (x: string, y: number) => number) {}

test(pipe(
  (x, y) => x.toUpperCase() + y.toPrecision(),
// ^? (parameter) x: string
  (x) => +x
))

// Spread arguments example
const result4 = pipe(
//    ^? const result4: string | number
// This became an array, not a tuple. Therefore there is no "last" function.
//   We make our best guess which is a union of all returns.
  ...[
    (s:any) => 123,
    (s:any) => "123"
  ]
)(42);
Read more comments on GitHub >

github_iconTop Results From Across the Web

Pipe Flow Expert - Modelling Positive Displacement Pumps
Generally there are three different types of PD pumps, namely rotary type, ... piston or a diaphragm, and linear type PD pumps rely...
Read more >
Pipe Flow Considerstions
Pipe Flow Considerations. Flow conditions: Laminar or turbulent: transition Reynolds number. Re = rVD/m=2,300. That is: Re<2,300 laminar; Re>4,000 turbulent ...
Read more >
Product Support - SLB
PIPEFLO Support. PIPESIM Support. asdf. ProdOps Support. ProSource Support. asdf. SeaBed Support. asdf. Symmetry Support. asdf. UniQ Support. VISAGE Support.
Read more >
Pipe Flow Module Updates - COMSOL® 6.1 Release Highlights
There is a new Pipe Wall Heat Transfer multiphysics coupling available for connecting: Domains that are modeled using the Heat Transfer interfaces; Edges ......
Read more >
Pipeflow: An Efficient Task-Parallel Pipeline Programming ...
model parallel executions through a linear chain of stages. ... ment of tf::Pipeflow type which is created by the sched- uler at runtime....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found