Skip to content

Various issues when trying to use pipe function #29904

Description

TypeScript Version: 3.3.1

Search Terms: pipe compose functional programming composition generics overloads

Code

It goes without saying that pipe is a very common utility function for composing functions, used often in functional programming. I've been using a pipe function in TypeScript for about 2 years, and over time I've collected numerous issues.

I wanted to create an issue to collect all of this information, to help others who want to use pipe so they are aware of the various footguns—and also for the TypeScript team to help their visibility of these issues.

To the best of my ability, I have narrowed these bugs down to the simplest examples, and provided any interesting (and sometimes useful) workarounds. I would appreciate any help in narrowing these examples further—perhaps even combining them where perceived issues are artefacts of the same underlying problems.

In my experience, 80% of the time pipe just works. These issues cover the remaining 20% of the time. Issue 1 is by far the most significant. The rest are unordered.

I have linked to sub issues where I'm aware they exist. For those without linked issues, we may want to create new issues to trick them independently.

1. Generics are lost when first composed function is generic

Related issues:

declareconstpipe: {<A,B,C>(ab: (a: A)=>B,bc: (b: B)=>C): (a: A)=>C;};typeComponent<P>=(props: P)=>{};declareconstmyHoc1: <P>(C: Component<P>)=>Component<P>;declareconstmyHoc2: <P>(C: Component<P>)=>Component<P>;declareconstMyComponent1: Component<{foo: 1}>;// When `strictFunctionTypes` disabled:// Expected type: `(a: Component<{ foo: 1 }>) => Component<{ foo: 1 }>`// Actual type: `(a: {}) => Component<{}>`constenhance=pipe(/* When `strictFunctionTypes` enabled, unexpected type error:: Argument of type '<P>(C: Component<P>) => Component<P>' is not assignable to parameter of type '(a: {}) => Component<{}>'. Types of parameters 'C' and 'a' are incompatible. Type '{}' is not assignable to type 'Component<{}>'. Type '{}' provides no match for the signature '(props: {}): {}'. */myHoc1,myHoc2,);// Expected type: `Component<{ foo: 1 }>`// Actual type: `Component<{}>`constMyComponent2=enhance(MyComponent1);// Workaround:constenhance2=pipe(()=>myHoc1(MyComponent1),myHoc2,);constMyComponent3=enhance2({});

With the option suggested in #27288, TypeScript would at least alert the developer to change the code to workaround this problem.

2. Incorrect overload is used for pipe

  • when strictFunctionTypes is disabled
  • when first composed function parameter is optional
  • when first pipe overload is zero parameters for first function

Related issues: #29913

declareconstpipe: {// 0-argument first function// Workaround: disable this overload<A>(a: ()=>A): ()=>A;// 1-argument first function<A,B>(ab: (a: A)=>B): (a: A)=>B;};// Expected type: `(a: {} | undefined) => number`// Actual type: `() => number`constfn=pipe((_a?: {})=>1);

3. Inference does not work when first pipe overload is zero parameters for first function

Related issues:

declareconstpipe: {// 0-argument first function// Workaround: disable this overload<A,B>(a: ()=>A,ab: (a: A)=>B): ()=>B;// 1-argument first function<A,B,C>(ab: (a: A)=>B,bc: (b: B)=>C): (a: A)=>C;};// Example 1typeFn=(n: number)=>number;constfn: Fn=pipe(// Expected param `x` type to be inferred as `number`// Actual type: anyx=>x+1,x=>x*2,);// Example 2constpromise=Promise.resolve(1);promise.then(pipe(// Expected param `x` type to be inferred as `number`// Actual type: anyx=>x+1,x=>x*2,),);

4. Untitled

Related issues:

declareconstpipe: {// Workaround 1: enable this overload// <A, B, C>(ab: (a: A) => B, bc: (b: B) => C): (a: A) => C;<A,B,C,D>(ab: (a: A)=>B,bc: (b: B)=>C,cd: (c: C)=>D): (a: A)=>D;};declareconstgetString: ()=>string;declareconstorUndefined: (name: string)=>string|undefined;declareconstidentity: <T>(value: T)=>T;constfn=pipe(getString,/* Unexpected type error: Type 'string | undefined' is not assignable to type '{}'. Type 'undefined' is not assignable to type '{}'. */string=>orUndefined(string),// Workaround 2: pass the function directly, instead of wrapping:// get,identity,);

5. Incorrect overload is used for composed function

Related issues:

declareconstpipe: {<A,B,C>(ab: (a: A)=>B,bc: (b: B)=>C): (a: A)=>C;};declareconstmyFn: {(p: string): string;(p: any): number;};declareconstgetString: ()=>string;// Expected type: `(a: {}) => string`// Actual type: `(a: {}) => number`// Note: if we comment out the last overload for `myFn`, we get the expected// type.constfn=pipe(getString,myFn,);

6. Untitled

Related issues:

declareconstpipe: {<A,B,C,D>(ab: (a: A)=>B,bc: (b: B)=>C,cd: (c: C)=>D): (a: A)=>D;};declareconstgetArray: ()=>string[];declareconstfirst: <T>(ts: T[])=>T;// When `strictFunctionTypes` disabled:// Expected type: `(a: {}) => string`// Actual type: `(a: {}) => {}`constfn=pipe(getArray,x=>x,/* When `strictFunctionTypes` enabled, unexpected type error: Argument of type '<T>(ts: T[]) => T' is not assignable to parameter of type '(c: {}) => {}'. Types of parameters 'ts' and 'c' are incompatible. Type '{}' is missing the following properties from type '{}[]': length, pop, push, concat, and 25 more. */first,);// Workaround 1: use `identity` functiondeclareconstidentity: <T>(value: T)=>T;constfn2=pipe(getArray,identity,first,);// Workaround 2: wrap last functionconstfn3=pipe(getArray,x=>x,x=>first(x),);

Metadata

Metadata

Assignees

No one assigned

    Labels

    Meta-IssueAn issue about the team, or the direction of TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions