简体   繁体   English

如何在打字稿中键入管道函数?

[英]How do I typed a pipe function in typescript?

Here is a pipe function in plain ol' js:这是普通 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.可悲的是,这在 Typescript 中目前是不可能的,除非您准备为您可能想要的每个长度定义pipe ,这似乎不是很有趣。

But you can get close!但是你可以靠近!

This example uses a Promise -inspired then to chain functions, but you could rename it if you want.此示例使用受Promise启发的then来链接函数,但您可以根据需要重命名它。

// 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 在 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.在幕后,它使用您的 JS 实现(以类型安全的方式实现它是可能的,但很费力),在现实世界中,您可能只是将它放在一个 JS 文件中,将此签名更改为declare function ,然后删除执行。 TS won't let you do that in a single file without complaining though, so I just wired it up manually for the example. TS 不会让您在没有抱怨的情况下在单个文件中执行此操作,因此我只是为示例手动连接了它。

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 在 TS Playground 上看看这个怪物

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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