Rest参数与可选参数一起重载时

huangapple go评论60阅读模式
英文:

Rest parameters when overloading with optional

问题

在重载函数时,有时我会使用以下模式:

export function foo(a: string): void;
export function foo(b: number): void;
export function foo(a: string, b: number): void;
export function foo(...args: [string] | [number] | [string, number]): void {
	/* ... */
}

这样可以轻松确定使用哪个原型,例如,如果 args.length === 2,则已知 args[string, number]。对于更复杂的类型(考虑如果你有两个接口而不是 stringnumber),这可以简化很多事情。

但是,在使用可选参数时,我无法使其工作:

export function foo(a?: string): void;
//              ~~~ error TS2394: This overload signature is not compatible with its implementation signature.
export function foo(...args: [] | [string]): void {
	return;
}

我知道可以简单地添加另一个不带可选参数的重载,但组合将迅速变得难以管理。

export function foo(): void;
export function foo(a: string): void;
export function foo(...args: [] | [string]): void {
	return;
}

更具体的示例如下:

interface Foo {
  /* ... */
}

interface Bar {
  /* ... */
}

interface Baz {
  /* ... */
}

function awesomeFunction(foo?: Foo, bar?: Bar): void;
function awesomeFunction(baz: Baz, foo?: Foo, bar?: Bar): void;
function awesomeFunction(...args:
  | [Foo | undefined, Bar | undefined]
  | [Baz, Foo | undefined, Bar | undefined]
): void {
  /* ... */
}

由于我知道如果 args[0]Baz,我也会知道 args 中的其他元素。但是,如上所示,它不适用于 foo 函数。

逐个列出所有排列组合将会产生大量的变体,而逐个参数输入为 arg1?: Foo | Baz, arg2: Foo | Bar, arg3: Bar 意味着我必须对不可能的变体进行类型检查。

有没有方法解决这个问题?

英文:

When overloading functions I sometimes use the following pattern:

export function foo(a: string): void;
export function foo(b: number): void;
export function foo(a: string, b: number): void;
export function foo(...args: [string] | [number] | [string, number]): void {
	/* ... */
}

This makes it easy to determine which of the prototypes is used, e.g. if args.length === 2 then args is known to be [string, number]. With more complex types (consider if instead of string and number you have two interfaces) this can simplify a great deal.

However, I cannot get this working when using optional parameters:

export function foo(a?: string): void;
//              ~~~ error TS2394: This overload signature is not compatible with its implementation signature.
export function foo(...args: [] | [string]): void {
	return;
}

I know I can simply add another overload without the optional parameter but the combinations would quickly become unmanageable.

export function foo(): void;
export function foo(a: string): void;
export function foo(...args: [] | [string]): void {
	return;
}

A more concrete example of this would be:

interface Foo {
  /* ... */
}

interface Bar {
  /* ... */
}

interface Baz {
  /* ... */
}

function awesomeFunction(foo?: Foo, bar?: Bar): void;
function awesomeFunction(baz: Baz, foo?: Foo, bar?: Bar): void;
function awesomeFunction(...args:
  | [Foo | undefined, Bar | undefined]
  | [Baz, Foo | undefined, Bar | undefined]
): void {
  /* ... */
}

Since I know that if args[0] is a Baz I would know the rest of the elements in args as well. It does however not work as seen above with the foo function.

Typing out all the permutations would become a lot of variants and typing out each parameter as arg1?: Foo | Baz, arg2: Foo | Bar, arg3: Bar means I have to typecheck for impossible variants.

Is there a way to work around this issue?

答案1

得分: 2

这里有一个函数参数列表和元组类型之间的直接等价关系。特别是,像(x?: string) => void中的可选函数参数对应于像[string?](或者当你使用元组元素标签时,[x?: string])中的可选元组元素。

这意味着你可以像这样编写你的重载 实现:

function foo(a?: string): void;
function foo(...args: [string?]): void {
}

function awesomeFunction(foo?: Foo, bar?: Bar): void;
function awesomeFunction(baz: Baz, foo?: Foo, bar?: Bar): void;
function awesomeFunction(...args: [Foo?, Bar?] | [Baz, Foo?, Bar?]): void {
}

实际上,如果所有调用签名的返回类型都相同,你甚至不需要重载:

function bar(...args: [a?: string]): void { }
// bar(a?: string | undefined): void

当你调用接受剩余元组联合的函数时,它看起来像一个重载函数:

function awesomeFunction(...args:
  [foo?: Foo, bar?: Bar] | [baz: Baz, foo?: Foo, bar?: Bar]
): void { }
// 1/2 awesomeFunction(foo?: Foo | undefined, bar?: Bar | undefined): void
// 2/2 awesomeFunction(baz: Baz, foo?: Foo | undefined, bar?: Bar | undefined): void

虽然[string?][string] | []之间可能存在关系,但当可选元组元素可用时,强迫编译器看到这一点可能没有意义。

英文:

There's essentially a direct equivalence between function parameter lists and tuple types. In particular, optional function parameters like in (x?: string) => void correspond to optional tuple elements like [string?] (or, when you use tuple element labels, [x?: string]).

That means you could write your overload implementations like this:

function foo(a?: string): void;
function foo(...args: [string?]): void {
}

function awesomeFunction(foo?: Foo, bar?: Bar): void;
function awesomeFunction(baz: Baz, foo?: Foo, bar?: Bar): void;
function awesomeFunction(...args: [Foo?, Bar?] | [Baz, Foo?, Bar?]): void {
}

Indeed if the return types are identical for all the call signatures, you don't even need overloads:

function bar(...args: [a?: string]): void { }
// bar(a?: string | undefined): void

and a function accepting a union of rest tuples looks like an overloaded function when you call it:

function awesomeFunction(...args:
  [foo?: Foo, bar?: Bar] | [baz: Baz, foo?: Foo, bar?: Bar]
): void { }
// 1/2 awesomeFunction(foo?: Foo | undefined, bar?: Bar | undefined): void
// 2/2 awesomeFunction(baz: Baz, foo?: Foo | undefined, bar?: Bar | undefined): void

And while there are arguably a relationship between [string?] and [string] | [], there's probably no point in trying to force the compiler to see it when optional tuple elements are available.

Playground link to code

huangapple
  • 本文由 发表于 2023年5月6日 22:39:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76189472.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定