简体   繁体   中英

How do I typed a pipe function in typescript?

Here is a pipe function in plain ol' js:

 const pipe = (f, ...fs) => x => f === undefined ? x : pipe(...fs)(f(x)) const foo = pipe( x => x + 1, x => `hey look ${x * 2} a string!`, x => x.substr(0, x.length) + Array(5).join(x.substring(x.length - 1)), console.log ) foo(3) // hey look 8 a string!!!!!

(taken from this answer)

How do I write the same thing in typescript with types?

ie when i'm piping functions, I can get the type info from the return type of the last function for the current

Original (and still recommended) answer

Sadly, this is not currently possible in Typescript unless you're prepared to define pipe for every length you might want, which doesn't seem very fun.

But you can get close!

This example uses a Promise -inspired then to chain functions, but you could rename it if you want.

// Alias because TS function types get tedious fast
type Fn<A, B> = (_: A) => B;

// Describe the shape of Pipe. We can't actually use `class` because while TS
// supports application syntax in types, it doesn't in object literals or classes.
interface Pipe<A, B> extends Fn<A, B> {
  // More idiomatic in the land of FP where `pipe` has its origins would be
  // `map` / `fmap`, but this feels more familiar to the average JS/TS-er.
  then<C>(g: Fn<B, C>): Pipe<A, C>
}

// Builds the `id` function as a Pipe.
function pipe<A>(): Pipe<A, A> {
  // Accept a function, and promise to augment it.
  function _pipe<A, B>(f: Fn<A, B>): Pipe<A, B> {
    // Take our function and start adding stuff.
    return Object.assign(f, {
      // Accept a function to tack on, also with augmentations.
      then<C>(g: Fn<B, C>): Pipe<A, C> {
        // Compose the functions!
        return _pipe<A, C>(a => g(f(a)));
      }
    });
  }
  // Return an augmented `id`
  return _pipe(a => a);
}

const foo = pipe<number>()
  .then(x => x + 1)
  .then(x => `hey look ${x * 2} a string!`)
  .then(x => x.substr(0, x.length) + Array(5).join(x.substring(x.length - 1)))
  .then(console.log);

foo(3); // "hey look 8 a string!!!!!"

Check it out on Typescript Playground

Edit: danger zone

Here's an example of a flexibly sized definition which is finite in capacity but should be large enough for most applications, and you can always follow the pattern to extend it. I wouldn't recommend using this because it's super messy, but figured I'd throw it together for fun and to demonstrate the concept.

Under the hood it uses your JS implementation (implementing it in a type-safe way is possible but laborious), and in the real world you'd probably just put that in a JS file, change this signature to declare function , and remove the implementation. TS won't let you do that in a single file without complaining though, so I just wired it up manually for the example.

Note:

  • To avoid inference problems, you need to annotate either the type of the parameter to the first function in the chain, or the returned function. The latter seems tidier to me, so that's what I've used in my example
  • My conditional types leave some room for error. If you provide any undefined params between the first and last defined ones, the type I assert can potentially be incorrect. It should be possible to fix that though, I just can't face it right now 😉
    type Fn<A, B> = (_: A) => B;
    const Marker: unique symbol = Symbol();
    type Z = typeof Marker;

    function pipe<
      A,
      B,
      C = Z,
      D = Z,
      E = Z,
      F = Z,
      G = Z,
      H = Z,
      I = Z,
      J = Z,
      K = Z
    >(
      f: Fn<A, B>,
      g?: Fn<B, C>,
      h?: Fn<C, D>,
      i?: Fn<D, E>,
      j?: Fn<E, F>,
      k?: Fn<F, G>,
      l?: Fn<G, H>,
      m?: Fn<H, I>,
      n?: Fn<I, J>,
      o?: Fn<J, K>
    ): Fn<
      A,
      K extends Z
        ? J extends Z
          ? I extends Z
            ? H extends Z
              ? G extends Z
                ? F extends Z
                  ? E extends Z
                    ? D extends Z
                      ? C extends Z
                        ? B
                        : C
                      : D
                    : E
                  : F
                : G
              : H
            : I
          : J
        : K
    > {
      // @ts-ignore
      const pipe = (f, ...fs) => x => f === undefined ? x : pipe(...fs)(f(x));
      return pipe(f, g, h, i, j, k, l, m, n, o);
    }

    // Typechecks fine.
    const foo: Fn<number, void> = pipe(
      x => x + 1,
      x => `hey look ${x * 2} a string!`,
      x => x.substr(0, x.length) + Array(5).join(x.substring(x.length - 1)),
      console.log
    )

    foo(3) // hey look 8 a string!!!!!

    // Typechecks fine with fewer params.
    const bar: Fn<string, Date> = pipe(
      x => x + 1,
      _ => new Date()
    );
    console.log(bar("This string is ignored, but we'll put a date in console."));

Check out this monstrosity on TS Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM