Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define overloaded signatures for a const in typescript?

We can define overloaded functions like this in typescript:

function hello(name: number): void;
function hello(name: string): void;
function hello(name: number | string): void {
  // ...
}

I tried to define this function as a const, like:

const hello = (name: number | string): void => {
  // ...
}

But not sure how to declare the overload signatures on it:

(name: number): void; 
(name: string): void;

Please help, thanks

like image 271
Freewind Avatar asked Apr 22 '20 11:04

Freewind


People also ask

Can you overload methods 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.

What is function overloading 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.

What is a function signature What does it mean to overload a function?

A function's signature includes the function's name and the number, order and type of its formal parameters. Two overloaded functions must not have the same signature. The return value is not part of a function's signature.


Video Answer


1 Answers

Given your overloaded function statement hello, you can discover the type that the compiler infers for it yourself by writing a type alias to its type and inspecting with IntelliSense by hovering over it in your IDE:

type Hello = typeof hello;
/* type Hello = {
    (name: number): void;
    (name: string): void;
} */

Here you can see that Hello is considered to be an object type with two call signatures, representing the call signatures you declared in your overload list, in the same order.

You can also write an equivalent type as an intersection of function types:

type AlsoHello = ((name: string) => void) & ((name: number) => void);

If you have a function types F1 and F2, then F1 & F2 represents an overloaded function where the F1 signature is checked before the F2 signature. In the (increasingly outdated) TypeScript spec it says:

While it is generally true that A & B is equivalent to B & A, the order of the constituent types may matter when determining the call and construct signatures of the intersection type.

In the rest of the answer I'll use the object-type-with-multiple-call-signatures version instead of the intersection-of-arrow-function-signatures version.


Anyway, in this particular case where both signatures return void, you can rewrite hello as a const with no errors if you annotate it with the above type:

const helloConst: {
  (name: number): void;
  (name: string): void;
} = (name: number | string): void => {
  // ...
}

A caveat in general, though... overloaded function statements are not checked as strictly as the const assignment. Overloaded function statements allow the implementation signature's return type to match a union of the return types of the call signatures, even though this is not safe:

function goodbye(name: number): number;
function goodbye(name: string): string;
function goodbye(name: number | string): number | string {
  return typeof name === "number" ? name + 1 : name + "!";
}

There's no compiler error in the above. The implementation of goodbye() returns number | string, and you could change typeof name === "number" to typeof name !== "number" and the compiler still wouldn't warn you. This is usually considered a feature and not a bug.

But now if you wrote that as a const, you get the error:

const goodbyeConst: { // error!
  (name: number): number;
  (name: string): string;
} = (name: number | string): number | string =>
    typeof name === "number" ? name + 1 : name + "!";
// Type '(name: string | number) => string | number' 
// is not assignable to 
// type '{ (name: number): number; (name: string): string; }'.

The const assignment is checked more strictly, and the compiler (correctly) complains that you can't safely treat a function of type (name: string | number) => string | number as a function of type ((name: string) => string) & ((name: number) => number). After all, the implementation could always return string which meets the implementation signature but fails to match the number call signature.

Anyway the way to proceed in such a case is to use a type assertion instead of an annotation:

const goodbyeConstAssert = ((name: number | string): number | string =>
  typeof name === "number" ? name + 1 : name + "!") as { // error!
    (name: number): number;
    (name: string): string;
  }

That compiles without error.


Okay, hope that helps; good luck!

Playground link to code

like image 54
jcalz Avatar answered Sep 20 '22 08:09

jcalz