Why Typescript doesn't support function overloading in a right way?

做~自己de王妃 提交于 2020-07-06 11:05:39

问题


There are a lot of questions about how function overloading works in Typescript, (for instance, TypeScript function overloading). But there are no questions like 'why does it work in that way?' Now function overloading looks like that:

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
  }
}

I mean, Typescript was created to make programmer's life easier by adding some so-called 'syntactic sugar' which gives advantages of OOD. So why can't Typescript do this annoying stuff instead of programmer? For example, it may looks like:

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

And this Typescript code would be transpiled to the following Javascript code:

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');
  }
};

And I didn't find any reason, why Typescript does not work like this. Any ideas?


回答1:


It's an interesting exercise to think about how you would implement "true" function overloads in TypeScript if you wanted to. It's easy enough to have the compiler take a bunch of separate functions and make a single function out of them. But at runtime, this single function would have to know which of the several underlying functions to call, based on the number and types of arguments. The number of arguments can definitely be determined at runtime, but the types of the arguments are completely erased, so there's no way to implement that, and you're stuck. 🙁

Sure, you could violate one of TypeScript's design goals (specifically non-goal #5 about adding runtime type information), but that's not going to happen. It might seem obvious that when you're checking for number, you can output typeof xxx === 'number', but what would you output when checking for a user-defined interface? One way to deal with this is to ask the developer to supply, for each function overload, a user-defined type guard which determines if the arguments are the right types. But now it's in the realm of making developers specify pairs-of-things for each function overload, which is more complicated than the current TypeScript overload concept.

For fun, let's see how close you can get to this yourself as a library which expects functions-and-type-guards to build an overloaded function. How about something like this (assuming TS 3.1 or above):

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;
}

The makeOverloads() function takes a variable number of FunctionAndGuard arguments, and returns a single overloaded function. And try it:

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

It works. Yay?

To recap: it's not possible without some way at runtime to determine the types of arguments, which requires developer-specified type guarding in the general case. So you could either do overloading by asking developers for type guards for every overload, or by doing what they do now, by having a single implementation and multiple call signatures. The latter is simpler.

Hope that gives some insight. Good luck!




回答2:


Typescript was created to make programmer's life easier by adding some so-called 'syntactic sugar' which gives advantages of OOD.

This is not in fact one of the TypeScript design goals. You can read the goals here. Support for separate implementations for function overloads would fall under the non-goal of "rely[ing] on run-time type information".



来源:https://stackoverflow.com/questions/53351061/why-typescript-doesnt-support-function-overloading-in-a-right-way

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!