【问题标题】:How do I typed a pipe function in typescript?如何在打字稿中输入管道函数?
【发布时间】:2017-11-03 23:22:35
【问题描述】:

这是一个简单的 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)

如何在 typescript 中使用类型编写相同的内容?

即当我使用管道函数时,我可以从当前函数的最后一个函数的返回类型中获取类型信息

【问题讨论】:

标签: typescript


【解决方案1】:

原始(并且仍然推荐)答案

遗憾的是,目前这在 Typescript 中是不可能的,除非你准备好为你可能想要的每个长度定义 pipe,这似乎不是很有趣。

但你可以靠近!

此示例使用 Promise-inspired 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

编辑:危险区域

这是一个灵活大小定义的示例,它的容量有限,但对于大多数应用程序来说应该足够大,您始终可以按照该模式对其进行扩展。我不建议使用它,因为它非常混乱,但我想我会把它放在一起来娱乐并展示这个概念。

在底层它使用你的 JS 实现(以类型安全的方式实现它是可能的,但很费力),在现实世界中你可能只是把它放在一个 JS 文件中,将此签名更改为 declare function ,并删除实现。 TS 不会让你在一个文件中这样做而不抱怨,所以我只是手动连接它作为示例。

注意:

  • 为避免推理问题,您需要将参数类型注释到链中的第一个函数或返回的函数。后者对我来说似乎更整洁,所以这就是我在示例中使用的内容
  • 我的条件类型留下了一些错误空间。如果您在第一个和最后一个定义的参数之间提供任何未定义的参数,我断言的类型可能不正确。应该可以解决这个问题,我现在无法面对它?
    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

【讨论】:

  • 你知道在 typescript 4.0 的情况下这是否仍然不可能吗? typescriptlang.org/docs/handbook/release-notes/…
  • 不 :( -- 可变元组很强大,但它们不允许您根据前一个来描述无限系列的类型参数,这是这里需要的。你可以这样做它手动用于任意有限集,如果你真的想要,你可以编写一个带有非常大的手动编写的参数列表的单个版本,大量默认 undefineds 和大量条件类型。但是返回投资似乎很低。
  • 我已经编辑了我的答案以提供一个示例声明,该声明允许您使用默认类型参数、可选参数和条件类型的所需语法。我最终使用了一种独特的符号类型来避免函数返回潜在未定义值的问题。正如您所看到的,它有点混乱,但如果您对一些(可能非常高的)最大参数数感到满意,这是可能的。
猜你喜欢
  • 2021-06-30
  • 1970-01-01
  • 1970-01-01
  • 2022-08-05
  • 2020-10-15
  • 1970-01-01
  • 1970-01-01
  • 2018-09-05
  • 1970-01-01
相关资源
最近更新 更多