为什么Typescript不以正确的方式支持函数重载?

时间:2018-11-17 12:03:20

标签: typescript oop overloading

关于Typescript中的函数重载如何工作,存在很多问题(例如TypeScript function overloading)。但是没有像“为什么它会那样工作?”这样的问题。 现在,函数重载看起来像这样:

function foo(param1: number): void; 
function foo(param1: number, param2: string): void;

function foo(...args: any[]): void {
  if (args.length === 1 && typeof args[0] === 'number') {
    // implementation 1
  } else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string') {
    // implementation 2
  } else {
    // error: unknown signature
  }
}

我的意思是,Typescript的创建是通过添加一些具有OOD优势的所谓“语法糖”来使程序员的生活更轻松。那么,为什么Typescript不能代替程序员来做这些烦人的事情?例如,它可能看起来像:

function foo(param1: number): void { 
  // implementation 1 
}; 
function foo(param1: number, param2: string): void {
  // implementation 2 
};
foo(someNumber); // result 1
foo(someNumber, someString); // result 2
foo(someNumber, someNumber); // ts compiler error

此Typescript代码将被转换为以下Javascript代码:

function foo_1(param1) { 
  // implementation 1 
};
function foo_2(param1, param2) { 
  // implementation 2 
}; 
function foo(args) {
  if (args.length === 1 && typeof args[0] === 'number') {
    foo_1(args);
  } else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string') {
    foo_2(args);
  } else {
    throw new Error('Invalid signature');
  }
};

我没有找到任何原因,为什么Typescript无法像这样工作。有什么想法吗?

2 个答案:

答案 0 :(得分:3)

考虑如何在TypeScript中实现“ true”函数重载是一个有趣的练习。让编译器采用一堆单独的函数并从中分离出一个函数是很容易的。但是在运行时,该单个函数将必须根据参数的数量和类型知道要调用的几个基础函数中的哪个。参数的数量绝对可以在运行时确定,但是参数的类型完全是erased,因此无法实现,您将陷入困境。

当然,您可能违反TypeScript的设计目标之一(特别是关于添加运行时类型信息的non-goal #5),但这不会发生。似乎很明显,当您检查number时,可以输出typeof xxx === 'number',但是当检查用户定义的interface时将输出什么呢?解决此问题的一种方法是要求开发人员为每个函数重载提供user-defined type guard,它确定参数是否为正确的类型。但是现在这是让开发人员为每个函数重载指定事物对的领域,这比当前的TypeScript重载概念更为复杂。

为了好玩,让我们来看看您作为一个期望函数和类型卫士能够构建重载函数的库离您有多近。怎么样(假设TS 3.1或更高版本):

interface FunctionAndGuard<A extends any[]=any[], R=any, A2 extends any[]= A> {
  function: (...args: A) => R,
  argumentsGuard: (args: any[]) => args is A2
};
type AsAcceptableFunctionsAndGuards<F extends FunctionAndGuard[]> = { [K in keyof F]:
  F[K] extends FunctionAndGuard<infer A, infer R, infer A2> ?
  FunctionAndGuard<A2, R, A> : never
}
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type Lookup<T, K> = K extends keyof T ? T[K] : never;
type FunctionAndGuardsToOverload<F extends FunctionAndGuard[]> =
  Lookup<UnionToIntersection<F[number]>, 'function'>;

function makeOverloads<F extends FunctionAndGuard[]>(
  ...functionsAndGuards: F & AsAcceptableFunctionsAndGuards<F>
): FunctionAndGuardsToOverload<F> {
  return ((...args: any[]) =>
    functionsAndGuards.find(fg => fg.argumentsGuard(args))!.function(...args)) as any;
}

makeOverloads()函数采用可变数量的FunctionAndGuard自变量,并返回单个重载函数。并尝试:

function foo_1(param1: number): void {
  // implementation 1 
};
function foo_2(param1: number, param2: string): void {
  // implementation 2 
};

const foo = makeOverloads({
  function: foo_1,
  argumentsGuard: (args: any[]): args is [number] =>
    args.length === 1 && typeof args[0] === 'number'
}, {
    function: foo_2,
    argumentsGuard: (args: any[]): args is [number, string] =>
      args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string'
  }
);

foo(1); // okay
foo(1, "two"); // okay
foo(1, 2); // error

有效。好极了?

回顾一下:在运行时没有某种方法来确定参数的类型是不可能的,这通常需要开发人员指定的类型保护。因此,您可以通过要求开发人员为每个重载提供类型防护来进行重载,或者通过具有单个实现和多个调用签名的方式来进行重载。后者更简单。

希望能提供一些见识。祝你好运!

答案 1 :(得分:2)

  

Typescript的创建是通过添加一些具有OOD优势的所谓“语法糖”来使程序员的生活更轻松。

这实际上不是TypeScript设计目标之一。您可以阅读目标here。对函数重载的单独实现的支持将落在“依赖于运行时类型信息”的目标之内。