我们可以为通用参数模拟 "default arguments" 吗?
Can we simulate "default arguments" for generic parameters?
我构建了一个小型类型级库来简化形式
的函数对的编写
function a(...): T | undefined;
function b(...): T;
其中 b 是通过在返回 undefined 时抛出异常从 a 派生的。基本思路如下:
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");
}
现在我们可以将 a
和 b
组合成一个函数,该函数需要一个额外的参数来区分类型:
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
这样的函数同样容易?
编译器对您指定默认参数不满意,因为有人总能在调用时出现并手动指定泛型类型参数,如下所示:upperCaseIfYouCan<FailMode.CanFail>("");
。如果您不希望发生这种情况,您可以使用 type assertion to suppress that error, and additionally, you should give the F
type parameter its own 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
手动指定)。
好的,希望其中之一能给您一些指导。祝你好运!
我构建了一个小型类型级库来简化形式
的函数对的编写function a(...): T | undefined;
function b(...): T;
其中 b 是通过在返回 undefined 时抛出异常从 a 派生的。基本思路如下:
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");
}
现在我们可以将 a
和 b
组合成一个函数,该函数需要一个额外的参数来区分类型:
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
这样的函数同样容易?
编译器对您指定默认参数不满意,因为有人总能在调用时出现并手动指定泛型类型参数,如下所示:upperCaseIfYouCan<FailMode.CanFail>("");
。如果您不希望发生这种情况,您可以使用 type assertion to suppress that error, and additionally, you should give the F
type parameter its own 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
手动指定)。
好的,希望其中之一能给您一些指导。祝你好运!