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.

Various issues when trying to use `pipe` function

See original GitHub issue

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:

declare const pipe: {
    <A, B, C>(ab: (a: A) => B, bc: (b: B) => C): (a: A) => C;
};

type Component<P> = (props: P) => {};

declare const myHoc1: <P>(C: Component<P>) => Component<P>;
declare const myHoc2: <P>(C: Component<P>) => Component<P>;

declare const MyComponent1: Component<{ foo: 1 }>;

// When `strictFunctionTypes` disabled:
// Expected type: `(a: Component<{ foo: 1 }>) => Component<{ foo: 1 }>`
// Actual type: `(a: {}) => Component<{}>`
const enhance = 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<{}>`
const MyComponent2 = enhance(MyComponent1);

// Workaround:
const enhance2 = pipe(
    () => myHoc1(MyComponent1),
    myHoc2,
);
const MyComponent3 = enhance2({});

With the option suggested in https://github.com/Microsoft/TypeScript/issues/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: https://github.com/Microsoft/TypeScript/issues/29913

declare const pipe: {
    // 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`
const fn = pipe((_a?: {}) => 1);

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

Related issues:

declare const pipe: {
    // 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 1

type Fn = (n: number) => number;
const fn: Fn = pipe(
    // Expected param `x` type to be inferred as `number`
    // Actual type: any
    x => x + 1,
    x => x * 2,
);

// Example 2

const promise = Promise.resolve(1);
promise.then(
    pipe(
        // Expected param `x` type to be inferred as `number`
        // Actual type: any
        x => x + 1,
        x => x * 2,
    ),
);

4. Untitled

Related issues:

declare const pipe: {
    // 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;
};

declare const getString: () => string;
declare const orUndefined: (name: string) => string | undefined;
declare const identity: <T>(value: T) => T;

const fn = 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:

declare const pipe: {
    <A, B, C>(ab: (a: A) => B, bc: (b: B) => C): (a: A) => C;
};

declare const myFn: {
    (p: string): string;
    (p: any): number;
};

declare const getString: () => string;

// Expected type: `(a: {}) => string`
// Actual type: `(a: {}) => number`
// Note: if we comment out the last overload for `myFn`, we get the expected
// type.
const fn = pipe(
    getString,
    myFn,
);

6. Untitled

Related issues:

declare const pipe: {
    <A, B, C, D>(ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): (a: A) => D;
};

declare const getArray: () => string[];
declare const first: <T>(ts: T[]) => T;

// When `strictFunctionTypes` disabled:
// Expected type: `(a: {}) => string`
// Actual type: `(a: {}) => {}`
const fn = 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` function
declare const identity: <T>(value: T) => T;
const fn2 = pipe(
    getArray,
    identity,
    first,
);

// Workaround 2: wrap last function
const fn3 = pipe(
    getArray,
    x => x,
    x => first(x),
);

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:40
  • Comments:14 (13 by maintainers)

github_iconTop GitHub Comments

8reactions
ahejlsbergcommented, Mar 2, 2019

With #30114 and #30193 all but issue 1 (inferring higher order function types) and issue 5 (overload resolution in inference and type relationships) above are now fixed. I’m definitely eager to address higher order function type inference (tracked by #9366) and I think we’re getting closer to a workable solution.

7reactions
ahejlsbergcommented, Mar 4, 2019

Higher order function type inference now implemented in #30215.

Read more comments on GitHub >

github_iconTop Results From Across the Web

An Introduction to the Pipe Operator in R
Because it chains functions in a linear order, the pipe is less applicable to problems that include multidirectional relationships. · The pipe ......
Read more >
My aversion to pipes - From the bottom of the heap
Pipes are also OK for package development as long as only a handful of verbs are chained together. To debug a function using...
Read more >
18 Pipes | R for Data Science
You have multiple inputs or outputs. If there isn't one primary object being transformed, but two or more objects being combined together, don't...
Read more >
Problems with pipes in c - Stack Overflow
You might also observe that perror() returns to the calling code, so if the pipe fails, you still try to use it. –...
Read more >
Pipe operator (%>%) does not work - RStudio Community
This is a recurring problem for me - pipe operator from dplyr package doesn't work in many cases. ... Or if you really...
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