Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 374
Inflight Avatar asked Nov 17 '18 12:11

Inflight


People also ask

Does TypeScript support function overloading?

TypeScript provides the concept of function overloading. You can have multiple functions with the same name but different parameter types and return type. However, the number of parameters should be the same.

Why function overloading is not possible in JavaScript?

There is no real function overloading in JavaScript since it allows to pass any number of parameters of any type. You have to check inside the function how many arguments have been passed and what type they are.

What is overloading in TypeScript?

In TypeScript, function overloading, or method overloading, is the ability to create multiple methods with the same name and same return type, but a different number of parameters or different parameter types. So essentially, method overloading is allowed when – Function name is same.

Why do we need type overloading in TypeScript?

Function overloading in TypeScript lets you define functions that can be called in multiple ways. Using function overloading requires defining the overload signatures: a set of functions with parameter and return types, but without a body. These signatures indicate how the function should be invoked.


2 Answers

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".

like image 120
Matt McCutchen Avatar answered Oct 04 '22 22:10

Matt McCutchen


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!

like image 42
jcalz Avatar answered Oct 05 '22 00:10

jcalz