【问题标题】:Can we simulate "default arguments" for generic parameters?我们可以模拟泛型参数的“默认参数”吗?
【发布时间】:2020-11-27 03:58:48
【问题描述】:

我构建了一个小型类型级库来简化表单函数对的编写

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

如果返回未定义,则通过抛出异常从 a 派生 b。基本思路如下:

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 这样的函数同样容易?

【问题讨论】:

    标签: typescript generics type-inference


    【解决方案1】:

    编译器对您指定默认参数不满意,因为总有人可以在调用时手动指定泛型类型参数,例如:upperCaseIfYouCan&lt;FailMode.CanFail&gt;("");。如果您不希望发生这种情况,您可以使用 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-01
      • 1970-01-01
      • 2019-07-18
      • 1970-01-01
      • 1970-01-01
      • 2015-05-19
      • 2012-07-25
      相关资源
      最近更新 更多