我们可以模拟通用参数的“默认参数”吗?

时间:2020-08-06 20:20:46

标签: typescript generics type-inference

我建立了一个小的类型级别的库来简化形式形式的函数对的编写

function a(...): T | undefined;
function b(...): T;

其中b是从a派生的,如果返回undefined,则抛出异常。基本思路如下:

export enum FailMode { CanFail, CanNotFail }
export const canFail = FailMode.CanFail;
export const canNotFail = FailMode.CanNotFail

export type Failure<F extends FailMode> = F extends FailMode.CanFail ? undefined : never;
export type Maybe<T, F extends FailMode> = T | Failure<F>;

export function failure<F extends FailMode>(mode: F): Failure<F> {
  if (mode === canFail) return undefined as Failure<F>;
  throw new Error("failure");
}

现在,我们可以将ab合并为一个函数,该函数需要一个额外的参数来区分类型:

function upperCaseIfYouCan<F extends FailMode>(x: string | undefined, mode: F): Maybe<string, F> {
  if (x === undefined)
    return failure<F>(mode);
  return x.toUpperCase();
}

// both of these now work
let y: string = upperCaseIfYouCan("foo", canNotFail);          // can throw, but will never return undefined
let z: string | undefined = upperCaseIfYouCan("foo", canFail); // cannot throw, but can return undefined

现在,在大多数情况下,我需要canNotFail变体,并且我想知道是否有一种方法可以使它成为“默认”,因为我不必传递canNotFail参数在这种情况下,则可以执行以下操作:

let y: string = upperCaseIfYouCan("foo");  // can throw

我已经确定这不能通过默认参数来实现,因为默认参数的类型必须无法与F统一。有没有办法以不同的方式实现这一目标,例如,像upperCaseIfYouCan这样的函数定义起来同样容易?

1 个答案:

答案 0 :(得分:1)

编译器对您指定默认参数并不满意,因为有人总是可以在调用时随手手动指定通用类型参数,例如:upperCaseIfYouCan<FailMode.CanFail>("");。如果您不希望发生这种情况,则可以使用type assertion来抑制该错误,此外,还应为F类型参数提供自己的default,以便没有明显的选择对于F,编译器将选择您的默认值,而不是完整的FailMode

function upperCaseIfYouCan<F extends FailMode = FailMode.CanNotFail>(
    x: string | undefined,
    mode: F = canNotFail as F
): Maybe<string, F> {
    if (x === undefined)
        return failure<F>(mode);
    return x.toUpperCase();
}

这应该适合您的用例:

let str = upperCaseIfYouCan("foo", canNotFail); // string
let strOrUndef = upperCaseIfYouCan("foo", canFail); // string | undefined

// this is the behavior you want
str = upperCaseIfYouCan("foo"); // string

另一方面,您可以使用overloads处理两种不同的调用函数的方法。尽管泛型在概念上更优雅,但在这种情况下重载可能更直观:

function upperCaseIfYouCan(x: string | undefined, mode: FailMode.CanFail): string | undefined;
function upperCaseIfYouCan(x: string | undefined, mode?: FailMode.CanNotFail): string;
function upperCaseIfYouCan(
    x: string | undefined,
    mode: FailMode = FailMode.CanNotFail
) {
    if (x === undefined) return failure(mode);
    return x.toUpperCase();
}

let str = upperCaseIfYouCan("foo", canNotFail); // string
let strOrUndef = upperCaseIfYouCan("foo", canFail); // string | undefined

str = upperCaseIfYouCan("foo"); // string

实现中没有比带有类型断言的版本更安全的方法,但是至少不能轻易以错误的方式调用重载(没有F可以手动指定)。


好的,希望其中之一能给您一些指导。祝你好运!

Playground link to code