泛型函数中的默认参数

Kir*_*nko 1 typescript

我有zip具有签名的函数:

function zip<T, U, V>(ts: T[], us: U[], zipper: (t: T, u: U) => V): V[]
Run Code Online (Sandbox Code Playgroud)

我正在尝试为zipper参数分配一个默认值(t, u) => [t, u]

function zip<T, U, V>(
    ts: T[],
    us: U[],
    zipper: (t: T, u: U) => V = (t, u) => (<[T, U]>[t, u])
)
Run Code Online (Sandbox Code Playgroud)

这会产生(有点预期)关于(T, U) => [T, U]不可分配给(T, U) => V.

最后,我用一组有点难看的重载解决了这个问题:

export function zip<T, U>(ts: T[], us: U[]): [T, U][]
export function zip<T, U, V>(
    ts: T[],
    us: U[],
    zipper: (t: T, u: U) => V
): V[]
export function zip<T, U>(
    ts: T[],
    us: U[],
    zipper: (t: T, u: U) => [T, U] = (t, u) => [t, u]
): [T, U][] {
    /* ... */
}
Run Code Online (Sandbox Code Playgroud)

这种方法有两个问题:

  1. 基本上,签名zip(T[], U[]): [T, U][]出现两次(第一次重载和实现本身);
  2. 实现的签名不是最通用的签名,(尤其是在更复杂的情况下)可能会导致错误。

有没有更好的方法来做我想做的事?第一次尝试的错误是否是编译器错误(似乎不是,但如果是,它肯定会使解决方案更简单)?

jca*_*alz 6

这个错误是一个很好的错误;泛型类型参数由函数的调用者指定,而不是实现者。TypeScript 很乐意推断参数并免除开发人员指定它们的麻烦,但它们仍然是根据调用者的需要而不是实现者的需要来推断的。无论是谁调用手段zip()将被允许选择什么TU以及V他们想要的。TypeScript 正确地警告您,该函数的实现不能假设它V[T, U]. 使用原始签名和默认参数,调用者可以自由调用zip<string, number, boolean>(["a"],[1]). 是的,这太疯狂了,不,你不能实现它。因此,编译器正在帮助您解决警告。


重载是解决这个问题的合理方法。你的过载签名很好。至于实现签名,是的,你应该让它更通用,记住zipper参数必须是可选的。但是请注意,您必须断言默认值zipper返回 a V,因为编译器仍然无法根据实现签名保证这是真的(即使知道它是安全的,因为重载限制了可以进行的调用)。下面是一个例子:

export function zip<T, U>(ts: T[], us: U[]): [T, U][]
export function zip<T, U, V>(
    ts: T[],
    us: U[],
    zipper: (t: T, u: U) => V
): V[]
export function zip<T, U, V>(
    ts: T[],
    us: U[],
    zipper?: (t: T, u: U) => V  // note the question mark
): V[] {
  if (!zipper) {
    zipper = (t, u) => ([t, u] as any as V); // note the assertion
  }
  const ret: V[] = []
  const len = Math.min(ts.length, us.length);
  for (let i = 0; i < len; i++) {
    ret.push(zipper(ts[i], us[i]));
  }
  return ret;
}
Run Code Online (Sandbox Code Playgroud)

这基本上就是 TypeScript 中的重载所发生的情况。编译器在检查实现时并不真正理解重载的签名。一旦你使用重载,你就告诉编译器你将负责确保实现是安全的。

无论如何,希望有所帮助;祝你好运!